Merge "start new headers"
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/backup/Android.mk b/cmds/backup/Android.mk
index 508aec0..73af0bc 100644
--- a/cmds/backup/Android.mk
+++ b/cmds/backup/Android.mk
@@ -5,7 +5,7 @@
LOCAL_SRC_FILES:= backup.cpp
-LOCAL_SHARED_LIBRARIES := libcutils libutils
+LOCAL_SHARED_LIBRARIES := libcutils libutils libandroidfw
LOCAL_MODULE:= btool
diff --git a/cmds/bootanimation/Android.mk b/cmds/bootanimation/Android.mk
index 7d39912..8c46b21 100644
--- a/cmds/bootanimation/Android.mk
+++ b/cmds/bootanimation/Android.mk
@@ -9,6 +9,7 @@
LOCAL_SHARED_LIBRARIES := \
libcutils \
+ libandroidfw \
libutils \
libbinder \
libui \
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..f269e80
--- /dev/null
+++ b/cmds/stagefright/SimplePlayer.cpp
@@ -0,0 +1,646 @@
+/*
+ * 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(10000ll);
+ }
+ 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<ABuffer> buffer;
+ while (format->findBuffer(StringPrintf("csd-%d", j).c_str(), &buffer)) {
+ 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() {
+ ALOGV("onDoMoreStuff");
+ for (size_t i = 0; i < mStateByTrackIndex.size(); ++i) {
+ CodecState *state = &mStateByTrackIndex.editValueAt(i);
+
+ status_t err;
+ do {
+ size_t index;
+ err = state->mCodec->dequeueInputBuffer(&index);
+
+ if (err == OK) {
+ ALOGV("dequeued input buffer on track %d",
+ mStateByTrackIndex.keyAt(i));
+
+ state->mAvailInputBufferIndices.push_back(index);
+ } else {
+ ALOGV("dequeueInputBuffer on track %d returned %d",
+ mStateByTrackIndex.keyAt(i), err);
+ }
+ } while (err == OK);
+
+ do {
+ BufferInfo info;
+ err = state->mCodec->dequeueOutputBuffer(
+ &info.mIndex,
+ &info.mOffset,
+ &info.mSize,
+ &info.mPresentationTimeUs,
+ &info.mFlags);
+
+ if (err == OK) {
+ ALOGV("dequeued output buffer on track %d",
+ mStateByTrackIndex.keyAt(i));
+
+ 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);
+ } else {
+ ALOGV("dequeueOutputBuffer on track %d returned %d",
+ mStateByTrackIndex.keyAt(i), err);
+ }
+ } while (err == OK
+ || err == INFO_FORMAT_CHANGED
+ || err == INFO_OUTPUT_BUFFERS_CHANGED);
+ }
+
+ 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);
+
+ ALOGV("enqueued input data on track %d", trackIndex);
+
+ 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.",
+ mStateByTrackIndex.keyAt(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.",
+ mStateByTrackIndex.keyAt(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..ad246d2
--- /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(60);
+ 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/ActionMode.java b/core/java/android/view/ActionMode.java
index 0349a2b..c1c7fe2 100644
--- a/core/java/android/view/ActionMode.java
+++ b/core/java/android/view/ActionMode.java
@@ -24,7 +24,7 @@
* <div class="special reference">
* <h3>Developer Guides</h3>
* <p>For information about how to provide contextual actions with {@code ActionMode},
- * read the <a href="{@docRoot}guide/topics/ui/menu.html#context-menu">Menus</a>
+ * read the <a href="{@docRoot}guide/topics/ui/menus.html#context-menu">Menus</a>
* developer guide.</p>
* </div>
*/
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index c16eb31..0675a74 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;
@@ -13055,11 +13055,6 @@
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.REQUEST_LAYOUT);
}
- if (getAccessibilityNodeProvider() != null) {
- throw new IllegalStateException("Views with AccessibilityNodeProvider"
- + " can't have children.");
- }
-
mPrivateFlags |= FORCE_LAYOUT;
mPrivateFlags |= INVALIDATED;
@@ -14505,8 +14500,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/ViewGroup.java b/core/java/android/view/ViewGroup.java
index c68d77d..2848e88 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -2669,6 +2669,15 @@
return child.draw(canvas, this, drawingTime);
}
+ @Override
+ public void requestLayout() {
+ if (mChildrenCount > 0 && getAccessibilityNodeProvider() != null) {
+ throw new IllegalStateException("Views with AccessibilityNodeProvider"
+ + " can't have children.");
+ }
+ super.requestLayout();
+ }
+
/**
*
* @param enabled True if children should be drawn with layers, false otherwise.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 84ce2a4..f831d85f 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -771,19 +771,29 @@
return mLayoutRequested;
}
+ void invalidate() {
+ mDirty.set(0, 0, mWidth, mHeight);
+ scheduleTraversals();
+ }
+
public void invalidateChild(View child, Rect dirty) {
+ invalidateChildInParent(null, dirty);
+ }
+
+ public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
checkThread();
if (DEBUG_DRAW) Log.v(TAG, "Invalidate child: " + dirty);
+
if (dirty == null) {
- // Fast invalidation for GL-enabled applications; GL must redraw everything
invalidate();
- return;
+ return null;
}
+
if (mCurScrollY != 0 || mTranslator != null) {
mTempRect.set(dirty);
dirty = mTempRect;
if (mCurScrollY != 0) {
- dirty.offset(0, -mCurScrollY);
+ dirty.offset(0, -mCurScrollY);
}
if (mTranslator != null) {
mTranslator.translateRectInAppWindowToScreen(dirty);
@@ -792,19 +802,24 @@
dirty.inset(-1, -1);
}
}
- if (!mDirty.isEmpty() && !mDirty.contains(dirty)) {
+
+ final Rect localDirty = mDirty;
+ if (!localDirty.isEmpty() && !localDirty.contains(dirty)) {
mAttachInfo.mSetIgnoreDirtyState = true;
mAttachInfo.mIgnoreDirtyState = true;
}
- mDirty.union(dirty);
+
+ // Add the new dirty rect to the current one
+ localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom);
+ // Intersect with the bounds of the window to skip
+ // updates that lie outside of the visible region
+ localDirty.intersect(0, 0, mWidth, mHeight);
+
if (!mWillDrawSoon) {
scheduleTraversals();
}
- }
-
- void invalidate() {
- mDirty.set(0, 0, mWidth, mHeight);
- scheduleTraversals();
+
+ return null;
}
void setStopped(boolean stopped) {
@@ -815,13 +830,8 @@
}
}
}
-
- public ViewParent getParent() {
- return null;
- }
- public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
- invalidateChild(null, dirty);
+ public ViewParent getParent() {
return null;
}
@@ -2647,8 +2657,8 @@
mHasHadWindowFocus = true;
}
- if (hasWindowFocus && mView != null) {
- sendAccessibilityEvents();
+ if (hasWindowFocus && mView != null && mAccessibilityManager.isEnabled()) {
+ mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
}
}
} break;
@@ -4063,21 +4073,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 +4641,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 +4866,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 +4928,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 +4986,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 +4996,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 +5076,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 +5184,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..8ccc59c7 100644
--- a/core/java/android/webkit/BrowserFrame.java
+++ b/core/java/android/webkit/BrowserFrame.java
@@ -471,18 +471,6 @@
}
/**
- * We have received an SSL certificate for the main top-level page.
- * Used by the Android HTTP stack only.
- */
- void certificate(SslCertificate certificate) {
- if (mIsMainFrame) {
- // we want to make this call even if the certificate is null
- // (ie, the site is not secure)
- mCallbackProxy.onReceivedCertificate(certificate);
- }
- }
-
- /**
* Destroy all native components of the BrowserFrame.
*/
public void destroy() {
@@ -515,10 +503,6 @@
}
}
}
- if (!JniUtil.useChromiumHttpStack()) {
- WebViewWorker.getHandler().sendEmptyMessage(
- WebViewWorker.MSG_TRIM_CACHE);
- }
break;
}
@@ -767,10 +751,9 @@
} else if (mSettings.getAllowContentAccess() &&
url.startsWith(ANDROID_CONTENT)) {
try {
- // Strip off mimetype, for compatibility with ContentLoader.java
- // If we don't do this, we can fail to load Gmail attachments,
- // because the URL being loaded doesn't exactly match the URL we
- // have permission to read.
+ // Strip off MIME type. If we don't do this, we can fail to
+ // load Gmail attachments, because the URL being loaded doesn't
+ // exactly match the URL we have permission to read.
int mimeIndex = url.lastIndexOf('?');
if (mimeIndex != -1) {
url = url.substring(0, mimeIndex);
@@ -787,101 +770,11 @@
}
/**
- * Start loading a resource.
- * @param loaderHandle The native ResourceLoader that is the target of the
- * data.
- * @param url The url to load.
- * @param method The http method.
- * @param headers The http headers.
- * @param postData If the method is "POST" postData is sent as the request
- * body. Is null when empty.
- * @param postDataIdentifier If the post data contained form this is the form identifier, otherwise it is 0.
- * @param cacheMode The cache mode to use when loading this resource. See WebSettings.setCacheMode
- * @param mainResource True if the this resource is the main request, not a supporting resource
- * @param userGesture
- * @param synchronous True if the load is synchronous.
- * @return A newly created LoadListener object.
- */
- private LoadListener startLoadingResource(int loaderHandle,
- String url,
- String method,
- HashMap headers,
- byte[] postData,
- long postDataIdentifier,
- int cacheMode,
- boolean mainResource,
- boolean userGesture,
- boolean synchronous,
- String username,
- String password) {
- if (mSettings.getCacheMode() != WebSettings.LOAD_DEFAULT) {
- cacheMode = mSettings.getCacheMode();
- }
-
- if (method.equals("POST")) {
- // Don't use the cache on POSTs when issuing a normal POST
- // request.
- if (cacheMode == WebSettings.LOAD_NORMAL) {
- cacheMode = WebSettings.LOAD_NO_CACHE;
- }
- String[] ret = getUsernamePassword();
- if (ret != null) {
- String domUsername = ret[0];
- String domPassword = ret[1];
- maybeSavePassword(postData, domUsername, domPassword);
- }
- }
-
- // is this resource the main-frame top-level page?
- boolean isMainFramePage = mIsMainFrame;
-
- if (DebugFlags.BROWSER_FRAME) {
- Log.v(LOGTAG, "startLoadingResource: url=" + url + ", method="
- + method + ", postData=" + postData + ", isMainFramePage="
- + isMainFramePage + ", mainResource=" + mainResource
- + ", userGesture=" + userGesture);
- }
-
- // Create a LoadListener
- LoadListener loadListener = LoadListener.getLoadListener(mContext,
- this, url, loaderHandle, synchronous, isMainFramePage,
- mainResource, userGesture, postDataIdentifier, username, password);
-
- if (LoadListener.getNativeLoaderCount() > MAX_OUTSTANDING_REQUESTS) {
- // send an error message, so that loadListener can be deleted
- // after this is returned. This is important as LoadListener's
- // nativeError will remove the request from its DocLoader's request
- // list. But the set up is not done until this method is returned.
- loadListener.error(
- android.net.http.EventHandler.ERROR, mContext.getString(
- com.android.internal.R.string.httpErrorTooManyRequests));
- return loadListener;
- }
-
- // Note that we are intentionally skipping
- // inputStreamForAndroidResource. This is so that FrameLoader will use
- // the various StreamLoader classes to handle assets.
- FrameLoader loader = new FrameLoader(loadListener, mSettings, method,
- mCallbackProxy.shouldInterceptRequest(url));
- loader.setHeaders(headers);
- loader.setPostData(postData);
- // Set the load mode to the mode used for the current page.
- // If WebKit wants validation, go to network directly.
- loader.setCacheMode(headers.containsKey("If-Modified-Since")
- || headers.containsKey("If-None-Match") ?
- WebSettings.LOAD_NO_CACHE : cacheMode);
- loader.executeLoad();
- // Set referrer to current URL?
- return !synchronous ? loadListener : null;
- }
-
- /**
* If this looks like a POST request (form submission) containing a username
* and password, give the user the option of saving them. Will either do
* nothing, or block until the UI interaction is complete.
*
- * Called by startLoadingResource when using the Apache HTTP stack.
- * Called directly by WebKit when using the Chrome HTTP stack.
+ * Called directly by WebKit.
*
* @param postData The data about to be sent as the body of a POST request.
* @param username The username entered by the user (sniffed from the DOM).
@@ -1351,15 +1244,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/CacheLoader.java b/core/java/android/webkit/CacheLoader.java
deleted file mode 100644
index 0721045..0000000
--- a/core/java/android/webkit/CacheLoader.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.webkit;
-
-import android.net.http.Headers;
-import android.text.TextUtils;
-import android.webkit.JniUtil;
-
-/**
- * This class is a concrete implementation of StreamLoader that uses a
- * CacheResult as the source for the stream. The CacheResult stored mimetype
- * and encoding is added to the HTTP response headers.
- */
-class CacheLoader extends StreamLoader {
-
- CacheManager.CacheResult mCacheResult; // Content source
-
- /**
- * Constructs a CacheLoader for use when loading content from the cache.
- *
- * @param loadListener LoadListener to pass the content to
- * @param result CacheResult used as the source for the content.
- */
- CacheLoader(LoadListener loadListener, CacheManager.CacheResult result) {
- super(loadListener);
-
- assert !JniUtil.useChromiumHttpStack();
-
- mCacheResult = result;
- }
-
- @Override
- protected boolean setupStreamAndSendStatus() {
- mDataStream = mCacheResult.inStream;
- mContentLength = mCacheResult.contentLength;
- mLoadListener.status(1, 1, mCacheResult.httpStatusCode, "OK");
- return true;
- }
-
- @Override
- protected void buildHeaders(Headers headers) {
- StringBuilder sb = new StringBuilder(mCacheResult.mimeType);
- if (!TextUtils.isEmpty(mCacheResult.encoding)) {
- sb.append(';');
- sb.append(mCacheResult.encoding);
- }
- headers.setContentType(sb.toString());
-
- if (!TextUtils.isEmpty(mCacheResult.location)) {
- headers.setLocation(mCacheResult.location);
- }
-
- if (!TextUtils.isEmpty(mCacheResult.expiresString)) {
- headers.setExpires(mCacheResult.expiresString);
- }
-
- if (!TextUtils.isEmpty(mCacheResult.contentdisposition)) {
- headers.setContentDisposition(mCacheResult.contentdisposition);
- }
-
- if (!TextUtils.isEmpty(mCacheResult.crossDomain)) {
- headers.setXPermittedCrossDomainPolicies(mCacheResult.crossDomain);
- }
- }
-}
diff --git a/core/java/android/webkit/CacheManager.java b/core/java/android/webkit/CacheManager.java
index e2342e9..6a85e00 100644
--- a/core/java/android/webkit/CacheManager.java
+++ b/core/java/android/webkit/CacheManager.java
@@ -57,35 +57,8 @@
static final String HEADER_KEY_IFMODIFIEDSINCE = "if-modified-since";
static final String HEADER_KEY_IFNONEMATCH = "if-none-match";
- private static final String NO_STORE = "no-store";
- private static final String NO_CACHE = "no-cache";
- private static final String MAX_AGE = "max-age";
- private static final String MANIFEST_MIME = "text/cache-manifest";
-
- private static long CACHE_THRESHOLD = 6 * 1024 * 1024;
- private static long CACHE_TRIM_AMOUNT = 2 * 1024 * 1024;
-
- // 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;
-
- // trimCacheIfNeeded() is called when a page is fully loaded. But JavaScript
- // can load the content, e.g. in a slideshow, continuously, so we need to
- // trim the cache on a timer base too. endCacheTransaction() is called on a
- // timer base. We share the same timer with less frequent update.
- private static int mTrimCacheCount = 0;
- private static final int TRIM_CACHE_INTERVAL = 5;
-
- private static WebViewDatabase mDataBase;
private static File mBaseDir;
- // Flag to clear the cache when the CacheManager is initialized
- private static boolean mClearCacheOnInit = false;
-
/**
* Represents a resource stored in the HTTP cache. Instances of this class
* can be obtained by calling
@@ -262,51 +235,12 @@
* @param context The application context
*/
static void init(Context context) {
- if (JniUtil.useChromiumHttpStack()) {
- // This isn't actually where the real cache lives, but where we put files for the
- // purpose of getCacheFile().
- mBaseDir = new File(context.getCacheDir(), "webviewCacheChromiumStaging");
- if (!mBaseDir.exists()) {
- mBaseDir.mkdirs();
- }
- return;
- }
-
- mDataBase = WebViewDatabase.getInstance(context.getApplicationContext());
- mBaseDir = new File(context.getCacheDir(), "webviewCache");
- if (createCacheDirectory() && mClearCacheOnInit) {
- removeAllCacheFiles();
- mClearCacheOnInit = false;
- }
- }
-
- /**
- * Create the cache directory if it does not already exist.
- *
- * @return true if the cache directory didn't exist and was created.
- */
- static private boolean createCacheDirectory() {
- assert !JniUtil.useChromiumHttpStack();
-
+ // This isn't actually where the real cache lives, but where we put files for the
+ // purpose of getCacheFile().
+ mBaseDir = new File(context.getCacheDir(), "webviewCacheChromiumStaging");
if (!mBaseDir.exists()) {
- if(!mBaseDir.mkdirs()) {
- Log.w(LOGTAG, "Unable to create webviewCache directory");
- return false;
- }
- FileUtils.setPermissions(
- mBaseDir.toString(),
- FileUtils.S_IRWXU | FileUtils.S_IRWXG,
- -1, -1);
- // If we did create the directory, we need to flush
- // the cache database. The directory could be recreated
- // because the system flushed all the data/cache directories
- // to free up disk space.
- // delete rows in the cache database
- WebViewWorker.getHandler().sendEmptyMessage(
- WebViewWorker.MSG_CLEAR_CACHE);
- return true;
+ mBaseDir.mkdirs();
}
- return false;
}
/**
@@ -322,76 +256,15 @@
}
/**
- * 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;
- }
-
- // only called from WebViewWorkerThread
- // make sure to call enableTransaction/disableTransaction in pair
- static boolean enableTransaction() {
- assert !JniUtil.useChromiumHttpStack();
-
- if (++mRefCount == 1) {
- mDataBase.startCacheTransaction();
- return true;
- }
return false;
}
- // only called from WebViewWorkerThread
- // make sure to call enableTransaction/disableTransaction in pair
- static boolean disableTransaction() {
- assert !JniUtil.useChromiumHttpStack();
-
- if (--mRefCount == 0) {
- mDataBase.endCacheTransaction();
- return true;
- }
- return false;
- }
-
- // only called from WebViewWorkerThread
- // make sure to call startTransaction/endTransaction in pair
- static boolean startTransaction() {
- assert !JniUtil.useChromiumHttpStack();
-
- return mDataBase.startCacheTransaction();
- }
-
- // only called from WebViewWorkerThread
- // make sure to call startTransaction/endTransaction in pair
- static boolean endTransaction() {
- assert !JniUtil.useChromiumHttpStack();
-
- boolean ret = mDataBase.endCacheTransaction();
- if (++mTrimCacheCount >= TRIM_CACHE_INTERVAL) {
- mTrimCacheCount = 0;
- trimCacheIfNeeded();
- }
- return ret;
- }
-
/**
* Starts a cache transaction. Returns true if this is the only running
* transaction. Otherwise, this transaction is nested inside currently
@@ -433,9 +306,8 @@
return getCacheFile(url, 0, headers);
}
- private static CacheResult getCacheFileChromiumHttpStack(String url) {
- assert JniUtil.useChromiumHttpStack();
-
+ static CacheResult getCacheFile(String url, long postIdentifier,
+ Map<String, String> headers) {
CacheResult result = nativeGetCacheResult(url);
if (result == null) {
return null;
@@ -453,53 +325,6 @@
// system. If it is gone, what should we do?
return null;
}
- return result;
- }
-
- private static CacheResult getCacheFileAndroidHttpStack(String url,
- long postIdentifier) {
- assert !JniUtil.useChromiumHttpStack();
-
- String databaseKey = getDatabaseKey(url, postIdentifier);
- CacheResult result = mDataBase.getCache(databaseKey);
- if (result == null) {
- return null;
- }
- if (result.contentLength == 0) {
- if (!isCachableRedirect(result.httpStatusCode)) {
- // This should not happen. If it does, remove it.
- mDataBase.removeCache(databaseKey);
- return null;
- }
- } else {
- File src = new File(mBaseDir, result.localPath);
- try {
- // Open the file here so that even if it is deleted, the content
- // is still readable by the caller until close() is called.
- result.inStream = new FileInputStream(src);
- } catch (FileNotFoundException e) {
- // The files in the cache directory can be removed by the
- // system. If it is gone, clean up the database.
- mDataBase.removeCache(databaseKey);
- return null;
- }
- }
- return result;
- }
-
- static CacheResult getCacheFile(String url, long postIdentifier,
- Map<String, String> headers) {
- if (mDisabled) {
- return null;
- }
-
- CacheResult result = JniUtil.useChromiumHttpStack() ?
- getCacheFileChromiumHttpStack(url) :
- getCacheFileAndroidHttpStack(url, postIdentifier);
-
- if (result == null) {
- return null;
- }
// A null value for headers is used by CACHE_MODE_CACHE_ONLY to imply
// that we should provide the cache result even if it is expired.
@@ -538,70 +363,8 @@
*/
static CacheResult createCacheFile(String url, int statusCode,
Headers headers, String mimeType, boolean forceCache) {
- if (JniUtil.useChromiumHttpStack()) {
- // This method is public but hidden. We break functionality.
- return null;
- }
-
- return createCacheFile(url, statusCode, headers, mimeType, 0,
- forceCache);
- }
-
- static CacheResult createCacheFile(String url, int statusCode,
- Headers headers, String mimeType, long postIdentifier,
- 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.
- if (statusCode == 303) {
- // remove the saved cache if there is any
- mDataBase.removeCache(databaseKey);
- return null;
- }
-
- // like the other browsers, do not cache redirects containing a cookie
- // header.
- if (isCachableRedirect(statusCode) && !headers.getSetCookie().isEmpty()) {
- // remove the saved cache if there is any
- mDataBase.removeCache(databaseKey);
- return null;
- }
-
- CacheResult ret = parseHeaders(statusCode, headers, mimeType);
- if (ret == null) {
- // this should only happen if the headers has "no-store" in the
- // cache-control. remove the saved cache if there is any
- mDataBase.removeCache(databaseKey);
- } else {
- setupFiles(databaseKey, ret);
- try {
- ret.outStream = new FileOutputStream(ret.outFile);
- } catch (FileNotFoundException e) {
- // This can happen with the system did a purge and our
- // subdirectory has gone, so lets try to create it again
- if (createCacheDirectory()) {
- try {
- ret.outStream = new FileOutputStream(ret.outFile);
- } catch (FileNotFoundException e2) {
- // We failed to create the file again, so there
- // is something else wrong. Return null.
- return null;
- }
- } else {
- // Failed to create cache directory
- return null;
- }
- }
- ret.mimeType = mimeType;
- }
-
- return ret;
+ // This method is public but hidden. We break functionality.
+ return null;
}
/**
@@ -624,64 +387,24 @@
return;
}
- if (JniUtil.useChromiumHttpStack()) {
- // This method is exposed in the public API but the API provides no
- // way to obtain a new CacheResult object with a non-null output
- // stream ...
- // - CacheResult objects returned by getCacheFile() have a null
- // output stream.
- // - new CacheResult objects have a null output stream and no
- // setter is provided.
- // Since this method throws a null pointer exception in this case,
- // it is effectively useless from the point of view of the public
- // API.
- //
- // With the Chromium HTTP stack we continue to throw the same
- // exception for 'backwards compatibility' with the Android HTTP
- // stack.
- //
- // This method is not used from within this package with the
- // Chromium HTTP stack, and for public API use, we should already
- // have thrown an exception above.
- assert false;
- return;
- }
-
- if (!cacheRet.outFile.exists()) {
- // the file in the cache directory can be removed by the system
- return;
- }
-
- boolean redirect = isCachableRedirect(cacheRet.httpStatusCode);
- if (redirect) {
- // location is in database, no need to keep the file
- cacheRet.contentLength = 0;
- cacheRet.localPath = "";
- }
- if ((redirect || cacheRet.contentLength == 0)
- && !cacheRet.outFile.delete()) {
- Log.e(LOGTAG, cacheRet.outFile.getPath() + " delete failed.");
- }
- if (cacheRet.contentLength == 0) {
- return;
- }
-
- mDataBase.addCache(getDatabaseKey(url, postIdentifier), cacheRet);
-
- if (DebugFlags.CACHE_MANAGER) {
- Log.v(LOGTAG, "saveCacheFile for url " + url);
- }
- }
-
- static boolean cleanupCacheFile(CacheResult cacheRet) {
- assert !JniUtil.useChromiumHttpStack();
-
- try {
- cacheRet.outStream.close();
- } catch (IOException e) {
- return false;
- }
- return cacheRet.outFile.delete();
+ // This method is exposed in the public API but the API provides no
+ // way to obtain a new CacheResult object with a non-null output
+ // stream ...
+ // - CacheResult objects returned by getCacheFile() have a null
+ // output stream.
+ // - new CacheResult objects have a null output stream and no
+ // setter is provided.
+ // Since this method throws a null pointer exception in this case,
+ // it is effectively useless from the point of view of the public
+ // API.
+ //
+ // With the Chromium HTTP stack we continue to throw the same
+ // exception for 'backwards compatibility' with the Android HTTP
+ // stack.
+ //
+ // This method is not used from within this package, and for public API
+ // use, we should already have thrown an exception above.
+ assert false;
}
/**
@@ -690,21 +413,6 @@
* @return Whether the removal succeeded.
*/
static boolean removeAllCacheFiles() {
- // Note, this is called before init() when the database is
- // created or upgraded.
- if (mBaseDir == null) {
- // This method should not be called before init() when using the
- // chrome http stack
- assert !JniUtil.useChromiumHttpStack();
- // Init() has not been called yet, so just flag that
- // we need to clear the cache when init() is called.
- mClearCacheOnInit = true;
- return true;
- }
- // delete rows in the cache database
- if (!JniUtil.useChromiumHttpStack())
- WebViewWorker.getHandler().sendEmptyMessage(WebViewWorker.MSG_CLEAR_CACHE);
-
// delete cache files in a separate thread to not block UI.
final Runnable clearCache = new Runnable() {
public void run() {
@@ -729,323 +437,5 @@
return true;
}
- static void trimCacheIfNeeded() {
- assert !JniUtil.useChromiumHttpStack();
-
- if (mDataBase.getCacheTotalSize() > CACHE_THRESHOLD) {
- List<String> pathList = mDataBase.trimCache(CACHE_TRIM_AMOUNT);
- int size = pathList.size();
- for (int i = 0; i < size; i++) {
- File f = new File(mBaseDir, pathList.get(i));
- if (!f.delete()) {
- Log.e(LOGTAG, f.getPath() + " delete failed.");
- }
- }
- // remove the unreferenced files in the cache directory
- final List<String> fileList = mDataBase.getAllCacheFileNames();
- if (fileList == null) return;
- String[] toDelete = mBaseDir.list(new FilenameFilter() {
- public boolean accept(File dir, String filename) {
- if (fileList.contains(filename)) {
- return false;
- } else {
- return true;
- }
- }
- });
- if (toDelete == null) return;
- size = toDelete.length;
- for (int i = 0; i < size; i++) {
- File f = new File(mBaseDir, toDelete[i]);
- if (!f.delete()) {
- Log.e(LOGTAG, f.getPath() + " delete failed.");
- }
- }
- }
- }
-
- static void clearCache() {
- assert !JniUtil.useChromiumHttpStack();
-
- // delete database
- mDataBase.clearCache();
- }
-
- private static boolean isCachableRedirect(int statusCode) {
- if (statusCode == 301 || statusCode == 302 || statusCode == 307) {
- // as 303 can't be cached, we do not return true
- return true;
- } else {
- return false;
- }
- }
-
- private static String getDatabaseKey(String url, long postIdentifier) {
- assert !JniUtil.useChromiumHttpStack();
-
- if (postIdentifier == 0) return url;
- return postIdentifier + url;
- }
-
- @SuppressWarnings("deprecation")
- private static void setupFiles(String url, CacheResult cacheRet) {
- assert !JniUtil.useChromiumHttpStack();
-
- if (true) {
- // Note: SHA1 is much stronger hash. But the cost of setupFiles() is
- // 3.2% cpu time for a fresh load of nytimes.com. While a simple
- // String.hashCode() is only 0.6%. If adding the collision resolving
- // to String.hashCode(), it makes the cpu time to be 1.6% for a
- // fresh load, but 5.3% for the worst case where all the files
- // already exist in the file system, but database is gone. So it
- // needs to resolve collision for every file at least once.
- int hashCode = url.hashCode();
- StringBuffer ret = new StringBuffer(8);
- appendAsHex(hashCode, ret);
- String path = ret.toString();
- File file = new File(mBaseDir, path);
- if (true) {
- boolean checkOldPath = true;
- // Check hash collision. If the hash file doesn't exist, just
- // continue. There is a chance that the old cache file is not
- // same as the hash file. As mDataBase.getCache() is more
- // expansive than "leak" a file until clear cache, don't bother.
- // If the hash file exists, make sure that it is same as the
- // cache file. If it is not, resolve the collision.
- while (file.exists()) {
- if (checkOldPath) {
- CacheResult oldResult = mDataBase.getCache(url);
- if (oldResult != null && oldResult.contentLength > 0) {
- if (path.equals(oldResult.localPath)) {
- path = oldResult.localPath;
- } else {
- path = oldResult.localPath;
- file = new File(mBaseDir, path);
- }
- break;
- }
- checkOldPath = false;
- }
- ret = new StringBuffer(8);
- appendAsHex(++hashCode, ret);
- path = ret.toString();
- file = new File(mBaseDir, path);
- }
- }
- cacheRet.localPath = path;
- cacheRet.outFile = file;
- } else {
- // get hash in byte[]
- Digest digest = new SHA1Digest();
- int digestLen = digest.getDigestSize();
- byte[] hash = new byte[digestLen];
- int urlLen = url.length();
- byte[] data = new byte[urlLen];
- url.getBytes(0, urlLen, data, 0);
- digest.update(data, 0, urlLen);
- digest.doFinal(hash, 0);
- // convert byte[] to hex String
- StringBuffer result = new StringBuffer(2 * digestLen);
- for (int i = 0; i < digestLen; i = i + 4) {
- int h = (0x00ff & hash[i]) << 24 | (0x00ff & hash[i + 1]) << 16
- | (0x00ff & hash[i + 2]) << 8 | (0x00ff & hash[i + 3]);
- appendAsHex(h, result);
- }
- cacheRet.localPath = result.toString();
- cacheRet.outFile = new File(mBaseDir, cacheRet.localPath);
- }
- }
-
- private static void appendAsHex(int i, StringBuffer ret) {
- assert !JniUtil.useChromiumHttpStack();
-
- String hex = Integer.toHexString(i);
- switch (hex.length()) {
- case 1:
- ret.append("0000000");
- break;
- case 2:
- ret.append("000000");
- break;
- case 3:
- ret.append("00000");
- break;
- case 4:
- ret.append("0000");
- break;
- case 5:
- ret.append("000");
- break;
- case 6:
- ret.append("00");
- break;
- case 7:
- ret.append("0");
- break;
- }
- ret.append(hex);
- }
-
- private static CacheResult parseHeaders(int statusCode, Headers headers,
- String mimeType) {
- assert !JniUtil.useChromiumHttpStack();
-
- // if the contentLength is already larger than CACHE_MAX_SIZE, skip it
- if (headers.getContentLength() > CACHE_MAX_SIZE) return null;
-
- // The HTML 5 spec, section 6.9.4, step 7.3 of the application cache
- // process states that HTTP caching rules are ignored for the
- // purposes of the application cache download process.
- // At this point we can't tell that if a file is part of this process,
- // except for the manifest, which has its own mimeType.
- // TODO: work out a way to distinguish all responses that are part of
- // the application download process and skip them.
- if (MANIFEST_MIME.equals(mimeType)) return null;
-
- // TODO: if authenticated or secure, return null
- CacheResult ret = new CacheResult();
- ret.httpStatusCode = statusCode;
-
- ret.location = headers.getLocation();
-
- ret.expires = -1;
- ret.expiresString = headers.getExpires();
- if (ret.expiresString != null) {
- try {
- ret.expires = AndroidHttpClient.parseDate(ret.expiresString);
- } catch (IllegalArgumentException ex) {
- // Take care of the special "-1" and "0" cases
- if ("-1".equals(ret.expiresString)
- || "0".equals(ret.expiresString)) {
- // make it expired, but can be used for history navigation
- ret.expires = 0;
- } else {
- Log.e(LOGTAG, "illegal expires: " + ret.expiresString);
- }
- }
- }
-
- ret.contentdisposition = headers.getContentDisposition();
-
- ret.crossDomain = headers.getXPermittedCrossDomainPolicies();
-
- // lastModified and etag may be set back to http header. So they can't
- // be empty string.
- String lastModified = headers.getLastModified();
- if (lastModified != null && lastModified.length() > 0) {
- ret.lastModified = lastModified;
- }
-
- String etag = headers.getEtag();
- if (etag != null && etag.length() > 0) {
- ret.etag = etag;
- }
-
- String cacheControl = headers.getCacheControl();
- if (cacheControl != null) {
- String[] controls = cacheControl.toLowerCase().split("[ ,;]");
- boolean noCache = false;
- for (int i = 0; i < controls.length; i++) {
- if (NO_STORE.equals(controls[i])) {
- return null;
- }
- // According to the spec, 'no-cache' means that the content
- // must be re-validated on every load. It does not mean that
- // the content can not be cached. set to expire 0 means it
- // can only be used in CACHE_MODE_CACHE_ONLY case
- if (NO_CACHE.equals(controls[i])) {
- ret.expires = 0;
- noCache = true;
- // if cache control = no-cache has been received, ignore max-age
- // header, according to http spec:
- // If a request includes the no-cache directive, it SHOULD NOT
- // include min-fresh, max-stale, or max-age.
- } else if (controls[i].startsWith(MAX_AGE) && !noCache) {
- int separator = controls[i].indexOf('=');
- if (separator < 0) {
- separator = controls[i].indexOf(':');
- }
- if (separator > 0) {
- String s = controls[i].substring(separator + 1);
- try {
- long sec = Long.parseLong(s);
- if (sec >= 0) {
- ret.expires = System.currentTimeMillis() + 1000
- * sec;
- }
- } catch (NumberFormatException ex) {
- if ("1d".equals(s)) {
- // Take care of the special "1d" case
- ret.expires = System.currentTimeMillis() + 86400000; // 24*60*60*1000
- } else {
- Log.e(LOGTAG, "exception in parseHeaders for "
- + "max-age:"
- + controls[i].substring(separator + 1));
- ret.expires = 0;
- }
- }
- }
- }
- }
- }
-
- // According to RFC 2616 section 14.32:
- // HTTP/1.1 caches SHOULD treat "Pragma: no-cache" as if the
- // client had sent "Cache-Control: no-cache"
- if (NO_CACHE.equals(headers.getPragma())) {
- ret.expires = 0;
- }
-
- // According to RFC 2616 section 13.2.4, if an expiration has not been
- // explicitly defined a heuristic to set an expiration may be used.
- if (ret.expires == -1) {
- if (ret.httpStatusCode == 301) {
- // If it is a permanent redirect, and it did not have an
- // explicit cache directive, then it never expires
- ret.expires = Long.MAX_VALUE;
- } else if (ret.httpStatusCode == 302 || ret.httpStatusCode == 307) {
- // If it is temporary redirect, expires
- ret.expires = 0;
- } else if (ret.lastModified == null) {
- // When we have no last-modified, then expire the content with
- // in 24hrs as, according to the RFC, longer time requires a
- // warning 113 to be added to the response.
-
- // Only add the default expiration for non-html markup. Some
- // sites like news.google.com have no cache directives.
- if (!mimeType.startsWith("text/html")) {
- ret.expires = System.currentTimeMillis() + 86400000; // 24*60*60*1000
- } else {
- // Setting a expires as zero will cache the result for
- // forward/back nav.
- ret.expires = 0;
- }
- } else {
- // If we have a last-modified value, we could use it to set the
- // expiration. Suggestion from RFC is 10% of time since
- // last-modified. As we are on mobile, loads are expensive,
- // increasing this to 20%.
-
- // 24 * 60 * 60 * 1000
- long lastmod = System.currentTimeMillis() + 86400000;
- try {
- lastmod = AndroidHttpClient.parseDate(ret.lastModified);
- } catch (IllegalArgumentException ex) {
- Log.e(LOGTAG, "illegal lastModified: " + ret.lastModified);
- }
- long difference = System.currentTimeMillis() - lastmod;
- if (difference > 0) {
- ret.expires = System.currentTimeMillis() + difference / 5;
- } else {
- // last modified is in the future, expire the content
- // on the last modified
- ret.expires = lastmod;
- }
- }
- }
-
- return ret;
- }
-
private static native CacheResult nativeGetCacheResult(String url);
}
diff --git a/core/java/android/webkit/CallbackProxy.java b/core/java/android/webkit/CallbackProxy.java
index 95d9275..3a05bca 100644
--- a/core/java/android/webkit/CallbackProxy.java
+++ b/core/java/android/webkit/CallbackProxy.java
@@ -920,10 +920,6 @@
if (PERF_PROBE) {
mWebCoreThreadTime = SystemClock.currentThreadTimeMillis();
mWebCoreIdleTime = 0;
- if (!JniUtil.useChromiumHttpStack()) {
- // Network is only used with the Android HTTP stack.
- Network.getInstance(mContext).startTiming();
- }
// un-comment this if PERF_PROBE is true
// Looper.myQueue().setWaitCallback(mIdleCallback);
}
@@ -941,10 +937,6 @@
Log.d("WebCore", "WebCore thread used " +
(SystemClock.currentThreadTimeMillis() - mWebCoreThreadTime)
+ " ms and idled " + mWebCoreIdleTime + " ms");
- if (!JniUtil.useChromiumHttpStack()) {
- // Network is only used with the Android HTTP stack.
- Network.getInstance(mContext).stopTiming();
- }
}
Message msg = obtainMessage(PAGE_FINISHED, url);
sendMessage(msg);
diff --git a/core/java/android/webkit/ContentLoader.java b/core/java/android/webkit/ContentLoader.java
deleted file mode 100644
index d13210aa..0000000
--- a/core/java/android/webkit/ContentLoader.java
+++ /dev/null
@@ -1,98 +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.webkit;
-
-import android.net.http.EventHandler;
-import android.net.http.Headers;
-import android.net.Uri;
-
-/**
- * This class is a concrete implementation of StreamLoader that loads
- * "content:" URIs
- */
-class ContentLoader extends StreamLoader {
-
- private String mUrl;
- private String mContentType;
-
- /**
- * Construct a ContentLoader with the specified content URI
- *
- * @param rawUrl "content:" url pointing to content to be loaded. This url
- * is the same url passed in to the WebView.
- * @param loadListener LoadListener to pass the content to
- */
- ContentLoader(String rawUrl, LoadListener loadListener) {
- super(loadListener);
-
- /* strip off mimetype */
- int mimeIndex = rawUrl.lastIndexOf('?');
- if (mimeIndex != -1) {
- mUrl = rawUrl.substring(0, mimeIndex);
- mContentType = rawUrl.substring(mimeIndex + 1);
- } else {
- mUrl = rawUrl;
- }
-
- }
-
- private String errString(Exception ex) {
- String exMessage = ex.getMessage();
- String errString = mContext.getString(
- com.android.internal.R.string.httpErrorFileNotFound);
- if (exMessage != null) {
- errString += " " + exMessage;
- }
- return errString;
- }
-
- @Override
- protected boolean setupStreamAndSendStatus() {
- Uri uri = Uri.parse(mUrl);
- if (uri == null) {
- mLoadListener.error(
- EventHandler.FILE_NOT_FOUND_ERROR,
- mContext.getString(
- com.android.internal.R.string.httpErrorBadUrl) +
- " " + mUrl);
- return false;
- }
-
- try {
- mDataStream = mContext.getContentResolver().openInputStream(uri);
- mLoadListener.status(1, 1, 200, "OK");
- } catch (java.io.FileNotFoundException ex) {
- mLoadListener.error(EventHandler.FILE_NOT_FOUND_ERROR, errString(ex));
- return false;
- } catch (RuntimeException ex) {
- // readExceptionWithFileNotFoundExceptionFromParcel in DatabaseUtils
- // can throw a serial of RuntimeException. Catch them all here.
- mLoadListener.error(EventHandler.FILE_ERROR, errString(ex));
- return false;
- }
- return true;
- }
-
- @Override
- protected void buildHeaders(Headers headers) {
- if (mContentType != null) {
- headers.setContentType("text/html");
- }
- // content can change, we don't want WebKit to cache it
- headers.setCacheControl("no-store, no-cache");
- }
-}
diff --git a/core/java/android/webkit/CookieManager.java b/core/java/android/webkit/CookieManager.java
index c500a76..497cab7 100644
--- a/core/java/android/webkit/CookieManager.java
+++ b/core/java/android/webkit/CookieManager.java
@@ -95,11 +95,6 @@
// max domain count to limit RAM cookie takes less than 100k,
private static final int MAX_RAM_DOMAIN_COUNT = 15;
- private Map<String, ArrayList<Cookie>> mCookieMap = new LinkedHashMap
- <String, ArrayList<Cookie>>(MAX_DOMAIN_COUNT, 0.75f, true);
-
- private boolean mAcceptCookie = true;
-
private int mPendingCookieOperations = 0;
/**
@@ -268,12 +263,7 @@
* cookies
*/
public synchronized void setAcceptCookie(boolean accept) {
- if (JniUtil.useChromiumHttpStack()) {
- nativeSetAcceptCookie(accept);
- return;
- }
-
- mAcceptCookie = accept;
+ nativeSetAcceptCookie(accept);
}
/**
@@ -282,11 +272,7 @@
* @return True if {@link WebView} instances send and accept cookies
*/
public synchronized boolean acceptCookie() {
- if (JniUtil.useChromiumHttpStack()) {
- return nativeAcceptCookie();
- }
-
- return mAcceptCookie;
+ return nativeAcceptCookie();
}
/**
@@ -299,20 +285,7 @@
* 'Set-Cookie' HTTP response header
*/
public void setCookie(String url, String value) {
- if (JniUtil.useChromiumHttpStack()) {
- setCookie(url, value, false);
- return;
- }
-
- WebAddress uri;
- try {
- uri = new WebAddress(url);
- } catch (ParseException ex) {
- Log.e(LOGTAG, "Bad address: " + url);
- return;
- }
-
- setCookie(uri, value);
+ setCookie(url, value, false);
}
/**
@@ -323,11 +296,6 @@
* @param privateBrowsing Whether to use the private browsing cookie jar
*/
void setCookie(String url, String value, boolean privateBrowsing) {
- if (!JniUtil.useChromiumHttpStack()) {
- setCookie(url, value);
- return;
- }
-
WebAddress uri;
try {
uri = new WebAddress(url);
@@ -340,136 +308,13 @@
}
/**
- * See {@link setCookie(String, String)}
- * @param uri The WebAddress for which the cookie is set
- * @param value The value of the cookie, as a string, using the format of
- * the 'Set-Cookie' HTTP response header
- */
- synchronized void setCookie(WebAddress uri, String value) {
- if (JniUtil.useChromiumHttpStack()) {
- nativeSetCookie(uri.toString(), value, false);
- return;
- }
-
- if (value != null && value.length() > MAX_COOKIE_LENGTH) {
- return;
- }
- if (!mAcceptCookie || uri == null) {
- return;
- }
- if (DebugFlags.COOKIE_MANAGER) {
- Log.v(LOGTAG, "setCookie: uri: " + uri + " value: " + value);
- }
-
- String[] hostAndPath = getHostAndPath(uri);
- if (hostAndPath == null) {
- return;
- }
-
- // For default path, when setting a cookie, the spec says:
- //Path: Defaults to the path of the request URL that generated the
- // Set-Cookie response, up to, but not including, the
- // right-most /.
- if (hostAndPath[1].length() > 1) {
- int index = hostAndPath[1].lastIndexOf(PATH_DELIM);
- hostAndPath[1] = hostAndPath[1].substring(0,
- index > 0 ? index : index + 1);
- }
-
- ArrayList<Cookie> cookies = null;
- try {
- cookies = parseCookie(hostAndPath[0], hostAndPath[1], value);
- } catch (RuntimeException ex) {
- Log.e(LOGTAG, "parse cookie failed for: " + value);
- }
-
- if (cookies == null || cookies.size() == 0) {
- return;
- }
-
- String baseDomain = getBaseDomain(hostAndPath[0]);
- ArrayList<Cookie> cookieList = mCookieMap.get(baseDomain);
- if (cookieList == null) {
- cookieList = CookieSyncManager.getInstance()
- .getCookiesForDomain(baseDomain);
- mCookieMap.put(baseDomain, cookieList);
- }
-
- long now = System.currentTimeMillis();
- int size = cookies.size();
- for (int i = 0; i < size; i++) {
- Cookie cookie = cookies.get(i);
-
- boolean done = false;
- Iterator<Cookie> iter = cookieList.iterator();
- while (iter.hasNext()) {
- Cookie cookieEntry = iter.next();
- if (cookie.exactMatch(cookieEntry)) {
- // expires == -1 means no expires defined. Otherwise
- // negative means far future
- if (cookie.expires < 0 || cookie.expires > now) {
- // secure cookies can't be overwritten by non-HTTPS url
- if (!cookieEntry.secure || HTTPS.equals(uri.getScheme())) {
- cookieEntry.value = cookie.value;
- cookieEntry.expires = cookie.expires;
- cookieEntry.secure = cookie.secure;
- cookieEntry.lastAcessTime = now;
- cookieEntry.lastUpdateTime = now;
- cookieEntry.mode = Cookie.MODE_REPLACED;
- }
- } else {
- cookieEntry.lastUpdateTime = now;
- cookieEntry.mode = Cookie.MODE_DELETED;
- }
- done = true;
- break;
- }
- }
-
- // expires == -1 means no expires defined. Otherwise negative means
- // far future
- if (!done && (cookie.expires < 0 || cookie.expires > now)) {
- cookie.lastAcessTime = now;
- cookie.lastUpdateTime = now;
- cookie.mode = Cookie.MODE_NEW;
- if (cookieList.size() > MAX_COOKIE_COUNT_PER_BASE_DOMAIN) {
- Cookie toDelete = new Cookie();
- toDelete.lastAcessTime = now;
- Iterator<Cookie> iter2 = cookieList.iterator();
- while (iter2.hasNext()) {
- Cookie cookieEntry2 = iter2.next();
- if ((cookieEntry2.lastAcessTime < toDelete.lastAcessTime)
- && cookieEntry2.mode != Cookie.MODE_DELETED) {
- toDelete = cookieEntry2;
- }
- }
- toDelete.mode = Cookie.MODE_DELETED;
- }
- cookieList.add(cookie);
- }
- }
- }
-
- /**
* Gets the cookies for the given URL.
* @param url The URL for which the cookies are requested
* @return value The cookies as a string, using the format of the 'Cookie'
* HTTP request header
*/
public String getCookie(String url) {
- if (JniUtil.useChromiumHttpStack()) {
- return getCookie(url, false);
- }
-
- WebAddress uri;
- try {
- uri = new WebAddress(url);
- } catch (ParseException ex) {
- Log.e(LOGTAG, "Bad address: " + url);
- return null;
- }
-
- return getCookie(uri);
+ return getCookie(url, false);
}
/**
@@ -481,11 +326,6 @@
* @hide Used by Browser, no intention to publish.
*/
public String getCookie(String url, boolean privateBrowsing) {
- if (!JniUtil.useChromiumHttpStack()) {
- // Just redirect to regular get cookie for android stack
- return getCookie(url);
- }
-
WebAddress uri;
try {
uri = new WebAddress(url);
@@ -506,76 +346,7 @@
* @hide Used by RequestHandle, no intention to publish.
*/
public synchronized String getCookie(WebAddress uri) {
- if (JniUtil.useChromiumHttpStack()) {
- return nativeGetCookie(uri.toString(), false);
- }
-
- if (!mAcceptCookie || uri == null) {
- return null;
- }
-
- String[] hostAndPath = getHostAndPath(uri);
- if (hostAndPath == null) {
- return null;
- }
-
- String baseDomain = getBaseDomain(hostAndPath[0]);
- ArrayList<Cookie> cookieList = mCookieMap.get(baseDomain);
- if (cookieList == null) {
- cookieList = CookieSyncManager.getInstance()
- .getCookiesForDomain(baseDomain);
- mCookieMap.put(baseDomain, cookieList);
- }
-
- long now = System.currentTimeMillis();
- boolean secure = HTTPS.equals(uri.getScheme());
- Iterator<Cookie> iter = cookieList.iterator();
-
- SortedSet<Cookie> cookieSet = new TreeSet<Cookie>(COMPARATOR);
- while (iter.hasNext()) {
- Cookie cookie = iter.next();
- if (cookie.domainMatch(hostAndPath[0]) &&
- cookie.pathMatch(hostAndPath[1])
- // expires == -1 means no expires defined. Otherwise
- // negative means far future
- && (cookie.expires < 0 || cookie.expires > now)
- && (!cookie.secure || secure)
- && cookie.mode != Cookie.MODE_DELETED) {
- cookie.lastAcessTime = now;
- cookieSet.add(cookie);
- }
- }
-
- StringBuilder ret = new StringBuilder(256);
- Iterator<Cookie> setIter = cookieSet.iterator();
- while (setIter.hasNext()) {
- Cookie cookie = setIter.next();
- if (ret.length() > 0) {
- ret.append(SEMICOLON);
- // according to RC2109, SEMICOLON is official separator,
- // but when log in yahoo.com, it needs WHITE_SPACE too.
- ret.append(WHITE_SPACE);
- }
-
- ret.append(cookie.name);
- if (cookie.value != null) {
- ret.append(EQUAL);
- ret.append(cookie.value);
- }
- }
-
- if (ret.length() > 0) {
- if (DebugFlags.COOKIE_MANAGER) {
- Log.v(LOGTAG, "getCookie: uri: " + uri + " value: " + ret);
- }
- return ret.toString();
- } else {
- if (DebugFlags.COOKIE_MANAGER) {
- Log.v(LOGTAG, "getCookie: uri: " + uri
- + " But can't find cookie.");
- }
- return null;
- }
+ return nativeGetCookie(uri.toString(), false);
}
/**
@@ -609,59 +380,20 @@
*/
public void removeSessionCookie() {
signalCookieOperationsStart();
- if (JniUtil.useChromiumHttpStack()) {
- new AsyncTask<Void, Void, Void>() {
- protected Void doInBackground(Void... none) {
- nativeRemoveSessionCookie();
- signalCookieOperationsComplete();
- return null;
- }
- }.execute();
- return;
- }
-
- final Runnable clearCache = new Runnable() {
- public void run() {
- synchronized(CookieManager.this) {
- Collection<ArrayList<Cookie>> cookieList = mCookieMap.values();
- Iterator<ArrayList<Cookie>> listIter = cookieList.iterator();
- while (listIter.hasNext()) {
- ArrayList<Cookie> list = listIter.next();
- Iterator<Cookie> iter = list.iterator();
- while (iter.hasNext()) {
- Cookie cookie = iter.next();
- if (cookie.expires == -1) {
- iter.remove();
- }
- }
- }
- CookieSyncManager.getInstance().clearSessionCookies();
- signalCookieOperationsComplete();
- }
+ new AsyncTask<Void, Void, Void>() {
+ protected Void doInBackground(Void... none) {
+ nativeRemoveSessionCookie();
+ signalCookieOperationsComplete();
+ return null;
}
- };
- new Thread(clearCache).start();
+ }.execute();
}
/**
* Removes all cookies.
*/
public void removeAllCookie() {
- if (JniUtil.useChromiumHttpStack()) {
- nativeRemoveAllCookie();
- return;
- }
-
- final Runnable clearCache = new Runnable() {
- public void run() {
- synchronized(CookieManager.this) {
- mCookieMap = new LinkedHashMap<String, ArrayList<Cookie>>(
- MAX_DOMAIN_COUNT, 0.75f, true);
- CookieSyncManager.getInstance().clearAllCookies();
- }
- }
- };
- new Thread(clearCache).start();
+ nativeRemoveAllCookie();
}
/**
@@ -669,11 +401,7 @@
* @return True if there are stored cookies.
*/
public synchronized boolean hasCookies() {
- if (JniUtil.useChromiumHttpStack()) {
- return hasCookies(false);
- }
-
- return CookieSyncManager.getInstance().hasCookies();
+ return hasCookies(false);
}
/**
@@ -682,10 +410,6 @@
* @hide Used by Browser, no intention to publish.
*/
public synchronized boolean hasCookies(boolean privateBrowsing) {
- if (!JniUtil.useChromiumHttpStack()) {
- return hasCookies();
- }
-
return nativeHasCookies(privateBrowsing);
}
@@ -693,34 +417,7 @@
* Removes all expired cookies.
*/
public void removeExpiredCookie() {
- if (JniUtil.useChromiumHttpStack()) {
- nativeRemoveExpiredCookie();
- return;
- }
-
- final Runnable clearCache = new Runnable() {
- public void run() {
- synchronized(CookieManager.this) {
- long now = System.currentTimeMillis();
- Collection<ArrayList<Cookie>> cookieList = mCookieMap.values();
- Iterator<ArrayList<Cookie>> listIter = cookieList.iterator();
- while (listIter.hasNext()) {
- ArrayList<Cookie> list = listIter.next();
- Iterator<Cookie> iter = list.iterator();
- while (iter.hasNext()) {
- Cookie cookie = iter.next();
- // expires == -1 means no expires defined. Otherwise
- // negative means far future
- if (cookie.expires > 0 && cookie.expires < now) {
- iter.remove();
- }
- }
- }
- CookieSyncManager.getInstance().clearExpiredCookies(now);
- }
- }
- };
- new Thread(clearCache).start();
+ nativeRemoveExpiredCookie();
}
/**
@@ -729,9 +426,7 @@
* Flush all cookies managed by the Chrome HTTP stack to flash.
*/
void flushCookieStore() {
- if (JniUtil.useChromiumHttpStack()) {
- nativeFlushCookieStore();
- }
+ nativeFlushCookieStore();
}
/**
@@ -741,11 +436,7 @@
* file scheme URLs
*/
public static boolean allowFileSchemeCookies() {
- if (JniUtil.useChromiumHttpStack()) {
- return nativeAcceptFileSchemeCookies();
- } else {
- return true;
- }
+ return nativeAcceptFileSchemeCookies();
}
/**
@@ -759,457 +450,7 @@
* {@link WebView} or CookieManager instance has been created.
*/
public static void setAcceptFileSchemeCookies(boolean accept) {
- if (JniUtil.useChromiumHttpStack()) {
- nativeSetAcceptFileSchemeCookies(accept);
- }
- }
-
- /**
- * Package level api, called from CookieSyncManager
- *
- * Get a list of cookies which are updated since a given time.
- * @param last The given time in millisec
- * @return A list of cookies
- */
- synchronized ArrayList<Cookie> getUpdatedCookiesSince(long last) {
- ArrayList<Cookie> cookies = new ArrayList<Cookie>();
- Collection<ArrayList<Cookie>> cookieList = mCookieMap.values();
- Iterator<ArrayList<Cookie>> listIter = cookieList.iterator();
- while (listIter.hasNext()) {
- ArrayList<Cookie> list = listIter.next();
- Iterator<Cookie> iter = list.iterator();
- while (iter.hasNext()) {
- Cookie cookie = iter.next();
- if (cookie.lastUpdateTime > last) {
- cookies.add(cookie);
- }
- }
- }
- return cookies;
- }
-
- /**
- * Package level api, called from CookieSyncManager
- *
- * Delete a Cookie in the RAM
- * @param cookie Cookie to be deleted
- */
- synchronized void deleteACookie(Cookie cookie) {
- if (cookie.mode == Cookie.MODE_DELETED) {
- String baseDomain = getBaseDomain(cookie.domain);
- ArrayList<Cookie> cookieList = mCookieMap.get(baseDomain);
- if (cookieList != null) {
- cookieList.remove(cookie);
- if (cookieList.isEmpty()) {
- mCookieMap.remove(baseDomain);
- }
- }
- }
- }
-
- /**
- * Package level api, called from CookieSyncManager
- *
- * Called after a cookie is synced to FLASH
- * @param cookie Cookie to be synced
- */
- synchronized void syncedACookie(Cookie cookie) {
- cookie.mode = Cookie.MODE_NORMAL;
- }
-
- /**
- * Package level api, called from CookieSyncManager
- *
- * Delete the least recent used domains if the total cookie count in RAM
- * exceeds the limit
- * @return A list of cookies which are removed from RAM
- */
- synchronized ArrayList<Cookie> deleteLRUDomain() {
- int count = 0;
- int byteCount = 0;
- int mapSize = mCookieMap.size();
-
- if (mapSize < MAX_RAM_DOMAIN_COUNT) {
- Collection<ArrayList<Cookie>> cookieLists = mCookieMap.values();
- Iterator<ArrayList<Cookie>> listIter = cookieLists.iterator();
- while (listIter.hasNext() && count < MAX_RAM_COOKIES_COUNT) {
- ArrayList<Cookie> list = listIter.next();
- if (DebugFlags.COOKIE_MANAGER) {
- Iterator<Cookie> iter = list.iterator();
- while (iter.hasNext() && count < MAX_RAM_COOKIES_COUNT) {
- Cookie cookie = iter.next();
- // 14 is 3 * sizeof(long) + sizeof(boolean)
- // + sizeof(byte)
- byteCount += cookie.domain.length()
- + cookie.path.length()
- + cookie.name.length()
- + (cookie.value != null
- ? cookie.value.length()
- : 0)
- + 14;
- count++;
- }
- } else {
- count += list.size();
- }
- }
- }
-
- ArrayList<Cookie> retlist = new ArrayList<Cookie>();
- if (mapSize >= MAX_RAM_DOMAIN_COUNT || count >= MAX_RAM_COOKIES_COUNT) {
- if (DebugFlags.COOKIE_MANAGER) {
- Log.v(LOGTAG, count + " cookies used " + byteCount
- + " bytes with " + mapSize + " domains");
- }
- Object[] domains = mCookieMap.keySet().toArray();
- int toGo = mapSize / 10 + 1;
- while (toGo-- > 0){
- String domain = domains[toGo].toString();
- if (DebugFlags.COOKIE_MANAGER) {
- Log.v(LOGTAG, "delete domain: " + domain
- + " from RAM cache");
- }
- retlist.addAll(mCookieMap.get(domain));
- mCookieMap.remove(domain);
- }
- }
- return retlist;
- }
-
- /**
- * Extract the host and path out of a uri
- * @param uri The given WebAddress
- * @return The host and path in the format of String[], String[0] is host
- * which has at least two periods, String[1] is path which always
- * ended with "/"
- */
- private String[] getHostAndPath(WebAddress uri) {
- if (uri.getHost() != null && uri.getPath() != null) {
-
- /*
- * The domain (i.e. host) portion of the cookie is supposed to be
- * case-insensitive. We will consistently return the domain in lower
- * case, which allows us to do the more efficient equals comparison
- * instead of equalIgnoreCase.
- *
- * See: http://www.ieft.org/rfc/rfc2965.txt (Section 3.3.3)
- */
- String[] ret = new String[2];
- ret[0] = uri.getHost().toLowerCase();
- ret[1] = uri.getPath();
-
- int index = ret[0].indexOf(PERIOD);
- if (index == -1) {
- if (uri.getScheme().equalsIgnoreCase("file")) {
- // There is a potential bug where a local file path matches
- // another file in the local web server directory. Still
- // "localhost" is the best pseudo domain name.
- ret[0] = "localhost";
- }
- } else if (index == ret[0].lastIndexOf(PERIOD)) {
- // cookie host must have at least two periods
- ret[0] = PERIOD + ret[0];
- }
-
- if (ret[1].charAt(0) != PATH_DELIM) {
- return null;
- }
-
- /*
- * find cookie path, e.g. for http://www.google.com, the path is "/"
- * for http://www.google.com/lab/, the path is "/lab"
- * for http://www.google.com/lab/foo, the path is "/lab/foo"
- * for http://www.google.com/lab?hl=en, the path is "/lab"
- * for http://www.google.com/lab.asp?hl=en, the path is "/lab.asp"
- * Note: the path from URI has at least one "/"
- * See:
- * http://www.unix.com.ua/rfc/rfc2109.html
- */
- index = ret[1].indexOf(QUESTION_MARK);
- if (index != -1) {
- ret[1] = ret[1].substring(0, index);
- }
-
- return ret;
- } else
- return null;
- }
-
- /**
- * Get the base domain for a give host. E.g. mail.google.com will return
- * google.com
- * @param host The give host
- * @return the base domain
- */
- private String getBaseDomain(String host) {
- int startIndex = 0;
- int nextIndex = host.indexOf(PERIOD);
- int lastIndex = host.lastIndexOf(PERIOD);
- while (nextIndex < lastIndex) {
- startIndex = nextIndex + 1;
- nextIndex = host.indexOf(PERIOD, startIndex);
- }
- if (startIndex > 0) {
- return host.substring(startIndex);
- } else {
- return host;
- }
- }
-
- /**
- * parseCookie() parses the cookieString which is a comma-separated list of
- * one or more cookies in the format of "NAME=VALUE; expires=DATE;
- * path=PATH; domain=DOMAIN_NAME; secure httponly" to a list of Cookies.
- * Here is a sample: IGDND=1, IGPC=ET=UB8TSNwtDmQ:AF=0; expires=Sun,
- * 17-Jan-2038 19:14:07 GMT; path=/ig; domain=.google.com, =,
- * PREF=ID=408909b1b304593d:TM=1156459854:LM=1156459854:S=V-vCAU6Sh-gobCfO;
- * expires=Sun, 17-Jan-2038 19:14:07 GMT; path=/; domain=.google.com which
- * contains 3 cookies IGDND, IGPC, PREF and an empty cookie
- * @param host The default host
- * @param path The default path
- * @param cookieString The string coming from "Set-Cookie:"
- * @return A list of Cookies
- */
- private ArrayList<Cookie> parseCookie(String host, String path,
- String cookieString) {
- ArrayList<Cookie> ret = new ArrayList<Cookie>();
-
- int index = 0;
- int length = cookieString.length();
- while (true) {
- Cookie cookie = null;
-
- // done
- if (index < 0 || index >= length) {
- break;
- }
-
- // skip white space
- if (cookieString.charAt(index) == WHITE_SPACE) {
- index++;
- continue;
- }
-
- /*
- * get NAME=VALUE; pair. detecting the end of a pair is tricky, it
- * can be the end of a string, like "foo=bluh", it can be semicolon
- * like "foo=bluh;path=/"; or it can be enclosed by \", like
- * "foo=\"bluh bluh\";path=/"
- *
- * Note: in the case of "foo=bluh, bar=bluh;path=/", we interpret
- * it as one cookie instead of two cookies.
- */
- int semicolonIndex = cookieString.indexOf(SEMICOLON, index);
- int equalIndex = cookieString.indexOf(EQUAL, index);
- cookie = new Cookie(host, path);
-
- // Cookies like "testcookie; path=/;" are valid and used
- // (lovefilm.se).
- // Look for 2 cases:
- // 1. "foo" or "foo;" where equalIndex is -1
- // 2. "foo; path=..." where the first semicolon is before an equal
- // and a semicolon exists.
- if ((semicolonIndex != -1 && (semicolonIndex < equalIndex)) ||
- equalIndex == -1) {
- // Fix up the index in case we have a string like "testcookie"
- if (semicolonIndex == -1) {
- semicolonIndex = length;
- }
- cookie.name = cookieString.substring(index, semicolonIndex);
- cookie.value = null;
- } else {
- cookie.name = cookieString.substring(index, equalIndex);
- // Make sure we do not throw an exception if the cookie is like
- // "foo="
- if ((equalIndex < length - 1) &&
- (cookieString.charAt(equalIndex + 1) == QUOTATION)) {
- index = cookieString.indexOf(QUOTATION, equalIndex + 2);
- if (index == -1) {
- // bad format, force return
- break;
- }
- }
- // Get the semicolon index again in case it was contained within
- // the quotations.
- semicolonIndex = cookieString.indexOf(SEMICOLON, index);
- if (semicolonIndex == -1) {
- semicolonIndex = length;
- }
- if (semicolonIndex - equalIndex > MAX_COOKIE_LENGTH) {
- // cookie is too big, trim it
- cookie.value = cookieString.substring(equalIndex + 1,
- equalIndex + 1 + MAX_COOKIE_LENGTH);
- } else if (equalIndex + 1 == semicolonIndex
- || semicolonIndex < equalIndex) {
- // this is an unusual case like "foo=;" or "foo="
- cookie.value = "";
- } else {
- cookie.value = cookieString.substring(equalIndex + 1,
- semicolonIndex);
- }
- }
- // get attributes
- index = semicolonIndex;
- while (true) {
- // done
- if (index < 0 || index >= length) {
- break;
- }
-
- // skip white space and semicolon
- if (cookieString.charAt(index) == WHITE_SPACE
- || cookieString.charAt(index) == SEMICOLON) {
- index++;
- continue;
- }
-
- // comma means next cookie
- if (cookieString.charAt(index) == COMMA) {
- index++;
- break;
- }
-
- // "secure" is a known attribute doesn't use "=";
- // while sites like live.com uses "secure="
- if (length - index >= SECURE_LENGTH
- && cookieString.substring(index, index + SECURE_LENGTH).
- equalsIgnoreCase(SECURE)) {
- index += SECURE_LENGTH;
- cookie.secure = true;
- if (index == length) break;
- if (cookieString.charAt(index) == EQUAL) index++;
- continue;
- }
-
- // "httponly" is a known attribute doesn't use "=";
- // while sites like live.com uses "httponly="
- if (length - index >= HTTP_ONLY_LENGTH
- && cookieString.substring(index,
- index + HTTP_ONLY_LENGTH).
- equalsIgnoreCase(HTTP_ONLY)) {
- index += HTTP_ONLY_LENGTH;
- if (index == length) break;
- if (cookieString.charAt(index) == EQUAL) index++;
- // FIXME: currently only parse the attribute
- continue;
- }
- equalIndex = cookieString.indexOf(EQUAL, index);
- if (equalIndex > 0) {
- String name = cookieString.substring(index, equalIndex).toLowerCase();
- int valueIndex = equalIndex + 1;
- while (valueIndex < length && cookieString.charAt(valueIndex) == WHITE_SPACE) {
- valueIndex++;
- }
-
- if (name.equals(EXPIRES)) {
- int comaIndex = cookieString.indexOf(COMMA, equalIndex);
-
- // skip ',' in (Wdy, DD-Mon-YYYY HH:MM:SS GMT) or
- // (Weekday, DD-Mon-YY HH:MM:SS GMT) if it applies.
- // "Wednesday" is the longest Weekday which has length 9
- if ((comaIndex != -1) &&
- (comaIndex - valueIndex <= 10)) {
- index = comaIndex + 1;
- }
- }
- semicolonIndex = cookieString.indexOf(SEMICOLON, index);
- int commaIndex = cookieString.indexOf(COMMA, index);
- if (semicolonIndex == -1 && commaIndex == -1) {
- index = length;
- } else if (semicolonIndex == -1) {
- index = commaIndex;
- } else if (commaIndex == -1) {
- index = semicolonIndex;
- } else {
- index = Math.min(semicolonIndex, commaIndex);
- }
- String value = cookieString.substring(valueIndex, index);
-
- // Strip quotes if they exist
- if (value.length() > 2 && value.charAt(0) == QUOTATION) {
- int endQuote = value.indexOf(QUOTATION, 1);
- if (endQuote > 0) {
- value = value.substring(1, endQuote);
- }
- }
- if (name.equals(EXPIRES)) {
- try {
- cookie.expires = AndroidHttpClient.parseDate(value);
- } catch (IllegalArgumentException ex) {
- Log.e(LOGTAG,
- "illegal format for expires: " + value);
- }
- } else if (name.equals(MAX_AGE)) {
- try {
- cookie.expires = System.currentTimeMillis() + 1000
- * Long.parseLong(value);
- } catch (NumberFormatException ex) {
- Log.e(LOGTAG,
- "illegal format for max-age: " + value);
- }
- } else if (name.equals(PATH)) {
- // only allow non-empty path value
- if (value.length() > 0) {
- cookie.path = value;
- }
- } else if (name.equals(DOMAIN)) {
- int lastPeriod = value.lastIndexOf(PERIOD);
- if (lastPeriod == 0) {
- // disallow cookies set for TLDs like [.com]
- cookie.domain = null;
- continue;
- }
- try {
- Integer.parseInt(value.substring(lastPeriod + 1));
- // no wildcard for ip address match
- if (!value.equals(host)) {
- // no cross-site cookie
- cookie.domain = null;
- }
- continue;
- } catch (NumberFormatException ex) {
- // ignore the exception, value is a host name
- }
- value = value.toLowerCase();
- if (value.charAt(0) != PERIOD) {
- // pre-pended dot to make it as a domain cookie
- value = PERIOD + value;
- lastPeriod++;
- }
- if (host.endsWith(value.substring(1))) {
- int len = value.length();
- int hostLen = host.length();
- if (hostLen > (len - 1)
- && host.charAt(hostLen - len) != PERIOD) {
- // make sure the bar.com doesn't match .ar.com
- cookie.domain = null;
- continue;
- }
- // disallow cookies set on ccTLDs like [.co.uk]
- if ((len == lastPeriod + 3)
- && (len >= 6 && len <= 8)) {
- String s = value.substring(1, lastPeriod);
- if (Arrays.binarySearch(BAD_COUNTRY_2LDS, s) >= 0) {
- cookie.domain = null;
- continue;
- }
- }
- cookie.domain = value;
- } else {
- // no cross-site or more specific sub-domain cookie
- cookie.domain = null;
- }
- }
- } else {
- // bad format, force return
- index = length;
- }
- }
- if (cookie != null && cookie.domain != null) {
- ret.add(cookie);
- }
- }
- return ret;
+ nativeSetAcceptFileSchemeCookies(accept);
}
// Native functions
diff --git a/core/java/android/webkit/CookieSyncManager.java b/core/java/android/webkit/CookieSyncManager.java
index a699800..19fa096 100644
--- a/core/java/android/webkit/CookieSyncManager.java
+++ b/core/java/android/webkit/CookieSyncManager.java
@@ -100,77 +100,6 @@
return sRef;
}
- /**
- * Package level api, called from CookieManager. Get all the cookies which
- * matches a given base domain.
- * @param domain
- * @return A list of Cookie
- */
- ArrayList<Cookie> getCookiesForDomain(String domain) {
- // null mDataBase implies that the host application doesn't support
- // persistent cookie. No sync needed.
- if (mDataBase == null) {
- return new ArrayList<Cookie>();
- }
-
- return mDataBase.getCookiesForDomain(domain);
- }
-
- /**
- * Package level api, called from CookieManager Clear all cookies in the
- * database
- */
- void clearAllCookies() {
- // null mDataBase implies that the host application doesn't support
- // persistent cookie.
- if (mDataBase == null) {
- return;
- }
-
- mDataBase.clearCookies();
- }
-
- /**
- * Returns true if there are any saved cookies.
- */
- boolean hasCookies() {
- // null mDataBase implies that the host application doesn't support
- // persistent cookie.
- if (mDataBase == null) {
- return false;
- }
-
- return mDataBase.hasCookies();
- }
-
- /**
- * Package level api, called from CookieManager Clear all session cookies in
- * the database
- */
- void clearSessionCookies() {
- // null mDataBase implies that the host application doesn't support
- // persistent cookie.
- if (mDataBase == null) {
- return;
- }
-
- mDataBase.clearSessionCookies();
- }
-
- /**
- * Package level api, called from CookieManager Clear all expired cookies in
- * the database
- */
- void clearExpiredCookies(long now) {
- // null mDataBase implies that the host application doesn't support
- // persistent cookie.
- if (mDataBase == null) {
- return;
- }
-
- mDataBase.clearExpiredCookies(now);
- }
-
protected void syncFromRamToFlash() {
if (DebugFlags.COOKIE_SYNC_MANAGER) {
Log.v(LOGTAG, "CookieSyncManager::syncFromRamToFlash STARTS");
@@ -182,41 +111,13 @@
return;
}
- if (JniUtil.useChromiumHttpStack()) {
- manager.flushCookieStore();
- } else {
- ArrayList<Cookie> cookieList = manager.getUpdatedCookiesSince(mLastUpdate);
- mLastUpdate = System.currentTimeMillis();
- syncFromRamToFlash(cookieList);
-
- ArrayList<Cookie> lruList = manager.deleteLRUDomain();
- syncFromRamToFlash(lruList);
- }
+ manager.flushCookieStore();
if (DebugFlags.COOKIE_SYNC_MANAGER) {
Log.v(LOGTAG, "CookieSyncManager::syncFromRamToFlash DONE");
}
}
- private void syncFromRamToFlash(ArrayList<Cookie> list) {
- Iterator<Cookie> iter = list.iterator();
- while (iter.hasNext()) {
- Cookie cookie = iter.next();
- if (cookie.mode != Cookie.MODE_NORMAL) {
- if (cookie.mode != Cookie.MODE_NEW) {
- mDataBase.deleteCookies(cookie.domain, cookie.path,
- cookie.name);
- }
- if (cookie.mode != Cookie.MODE_DELETED) {
- mDataBase.addCookie(cookie);
- CookieManager.getInstance().syncedACookie(cookie);
- } else {
- CookieManager.getInstance().deleteACookie(cookie);
- }
- }
- }
- }
-
private static void checkInstanceIsCreated() {
if (sRef == null) {
throw new IllegalStateException(
diff --git a/core/java/android/webkit/DataLoader.java b/core/java/android/webkit/DataLoader.java
deleted file mode 100644
index e8d9069..0000000
--- a/core/java/android/webkit/DataLoader.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.webkit;
-
-import android.net.http.EventHandler;
-
-import com.android.internal.R;
-
-import java.io.ByteArrayInputStream;
-
-import libcore.io.Base64;
-
-/**
- * This class is a concrete implementation of StreamLoader that uses the
- * content supplied as a URL as the source for the stream. The mimetype
- * optionally provided in the URL is extracted and inserted into the HTTP
- * response headers.
- */
-class DataLoader extends StreamLoader {
-
- /**
- * Constructor uses the dataURL as the source for an InputStream
- * @param dataUrl data: URL string optionally containing a mimetype
- * @param loadListener LoadListener to pass the content to
- */
- DataLoader(String dataUrl, LoadListener loadListener) {
- super(loadListener);
-
- String url = dataUrl.substring("data:".length());
- byte[] data = null;
- int commaIndex = url.indexOf(',');
- if (commaIndex != -1) {
- String contentType = url.substring(0, commaIndex);
- data = url.substring(commaIndex + 1).getBytes();
- loadListener.parseContentTypeHeader(contentType);
- if ("base64".equals(loadListener.transferEncoding())) {
- data = Base64.decode(data);
- }
- } else {
- data = url.getBytes();
- }
- if (data != null) {
- mDataStream = new ByteArrayInputStream(data);
- mContentLength = data.length;
- }
- }
-
- @Override
- protected boolean setupStreamAndSendStatus() {
- if (mDataStream != null) {
- mLoadListener.status(1, 1, 200, "OK");
- return true;
- } else {
- mLoadListener.error(EventHandler.ERROR,
- mContext.getString(R.string.httpError));
- return false;
- }
- }
-
- @Override
- protected void buildHeaders(android.net.http.Headers h) {
- }
-}
diff --git a/core/java/android/webkit/FileLoader.java b/core/java/android/webkit/FileLoader.java
deleted file mode 100644
index e21e9ef8..0000000
--- a/core/java/android/webkit/FileLoader.java
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.webkit;
-
-import com.android.internal.R;
-
-import android.content.res.AssetManager;
-import android.net.http.EventHandler;
-import android.net.http.Headers;
-import android.util.Log;
-import android.util.TypedValue;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.lang.reflect.Field;
-
-/**
- * This class is a concrete implementation of StreamLoader that uses a
- * file or asset as the source for the stream.
- *
- */
-class FileLoader extends StreamLoader {
-
- private String mPath; // Full path to the file to load
- private int mType; // Indicates the type of the load
- private boolean mAllowFileAccess; // Allow/block file system access
-
- // used for files under asset directory
- static final int TYPE_ASSET = 1;
- // used for files under res directory
- static final int TYPE_RES = 2;
- // generic file
- static final int TYPE_FILE = 3;
-
- private static final String LOGTAG = "webkit";
-
- /**
- * Construct a FileLoader with the file URL specified as the content
- * source.
- *
- * @param url Full file url pointing to content to be loaded
- * @param loadListener LoadListener to pass the content to
- * @param asset true if url points to an asset.
- * @param allowFileAccess true if this WebView is allowed to access files
- * on the file system.
- */
- FileLoader(String url, LoadListener loadListener, int type,
- boolean allowFileAccess) {
- super(loadListener);
- mType = type;
- mAllowFileAccess = allowFileAccess;
-
- // clean the Url
- int index = url.indexOf('?');
- if (mType == TYPE_ASSET) {
- mPath = index > 0 ? URLUtil.stripAnchor(
- url.substring(URLUtil.ASSET_BASE.length(), index)) :
- URLUtil.stripAnchor(url.substring(
- URLUtil.ASSET_BASE.length()));
- } else if (mType == TYPE_RES) {
- mPath = index > 0 ? URLUtil.stripAnchor(
- url.substring(URLUtil.RESOURCE_BASE.length(), index)) :
- URLUtil.stripAnchor(url.substring(
- URLUtil.RESOURCE_BASE.length()));
- } else {
- mPath = index > 0 ? URLUtil.stripAnchor(
- url.substring(URLUtil.FILE_BASE.length(), index)) :
- URLUtil.stripAnchor(url.substring(
- URLUtil.FILE_BASE.length()));
- }
- }
-
- private String errString(Exception ex) {
- String exMessage = ex.getMessage();
- String errString = mContext.getString(R.string.httpErrorFileNotFound);
- if (exMessage != null) {
- errString += " " + exMessage;
- }
- return errString;
- }
-
- @Override
- protected boolean setupStreamAndSendStatus() {
- try {
- if (mType == TYPE_ASSET) {
- try {
- mDataStream = mContext.getAssets().open(mPath);
- } catch (java.io.FileNotFoundException ex) {
- // try the rest files included in the package
- mDataStream = mContext.getAssets().openNonAsset(mPath);
- }
- } else if (mType == TYPE_RES) {
- // get the resource id from the path. e.g. for the path like
- // drawable/foo.png, the id is located at field "foo" of class
- // "<package>.R$drawable"
- if (mPath == null || mPath.length() == 0) {
- Log.e(LOGTAG, "Need a path to resolve the res file");
- mLoadListener.error(EventHandler.FILE_ERROR, mContext
- .getString(R.string.httpErrorFileNotFound));
- return false;
-
- }
- int slash = mPath.indexOf('/');
- int dot = mPath.indexOf('.', slash);
- if (slash == -1 || dot == -1) {
- Log.e(LOGTAG, "Incorrect res path: " + mPath);
- mLoadListener.error(EventHandler.FILE_ERROR, mContext
- .getString(R.string.httpErrorFileNotFound));
- return false;
- }
- String subClassName = mPath.substring(0, slash);
- String fieldName = mPath.substring(slash + 1, dot);
- String errorMsg = null;
- try {
- final Class<?> d = mContext.getApplicationContext()
- .getClassLoader().loadClass(
- mContext.getPackageName() + ".R$"
- + subClassName);
- final Field field = d.getField(fieldName);
- final int id = field.getInt(null);
- TypedValue value = new TypedValue();
- mContext.getResources().getValue(id, value, true);
- if (value.type == TypedValue.TYPE_STRING) {
- mDataStream = mContext.getAssets().openNonAsset(
- value.assetCookie, value.string.toString(),
- AssetManager.ACCESS_STREAMING);
- } else {
- errorMsg = "Only support TYPE_STRING for the res files";
- }
- } catch (ClassNotFoundException e) {
- errorMsg = "Can't find class: "
- + mContext.getPackageName() + ".R$" + subClassName;
- } catch (SecurityException e) {
- errorMsg = "Caught SecurityException: " + e;
- } catch (NoSuchFieldException e) {
- errorMsg = "Can't find field: " + fieldName + " in "
- + mContext.getPackageName() + ".R$" + subClassName;
- } catch (IllegalArgumentException e) {
- errorMsg = "Caught IllegalArgumentException: " + e;
- } catch (IllegalAccessException e) {
- errorMsg = "Caught IllegalAccessException: " + e;
- }
- if (errorMsg != null) {
- mLoadListener.error(EventHandler.FILE_ERROR, mContext
- .getString(R.string.httpErrorFileNotFound));
- return false;
- }
- } else {
- if (!mAllowFileAccess) {
- mLoadListener.error(EventHandler.FILE_ERROR,
- mContext.getString(R.string.httpErrorFileNotFound));
- return false;
- }
-
- mDataStream = new FileInputStream(mPath);
- mContentLength = (new File(mPath)).length();
- }
- mLoadListener.status(1, 1, 200, "OK");
-
- } catch (java.io.FileNotFoundException ex) {
- mLoadListener.error(EventHandler.FILE_NOT_FOUND_ERROR, errString(ex));
- return false;
-
- } catch (java.io.IOException ex) {
- mLoadListener.error(EventHandler.FILE_ERROR, errString(ex));
- return false;
- }
- return true;
- }
-
- @Override
- protected void buildHeaders(Headers headers) {
- // do nothing.
- }
-}
diff --git a/core/java/android/webkit/FrameLoader.java b/core/java/android/webkit/FrameLoader.java
deleted file mode 100644
index 0d80302..0000000
--- a/core/java/android/webkit/FrameLoader.java
+++ /dev/null
@@ -1,421 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.webkit;
-
-import android.net.http.ErrorStrings;
-import android.net.http.EventHandler;
-import android.net.http.RequestHandle;
-import android.os.Build;
-import android.util.Log;
-import android.webkit.CacheManager.CacheResult;
-import android.webkit.JniUtil;
-
-import java.util.HashMap;
-import java.util.Map;
-
-class FrameLoader {
-
- private final LoadListener mListener;
- private final String mMethod;
- private final WebSettings mSettings;
- private Map<String, String> mHeaders;
- private byte[] mPostData;
- private Network mNetwork;
- private int mCacheMode;
- private String mReferrer;
- private String mContentType;
- private final String mUaprofHeader;
- private final WebResourceResponse mInterceptResponse;
-
- private static final int URI_PROTOCOL = 0x100;
-
- private static final String CONTENT_TYPE = "content-type";
-
- // Contents of an about:blank page
- private static final String mAboutBlank =
- "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EB\">" +
- "<html><head><title>about:blank</title></head><body></body></html>";
-
- static final String HEADER_STR = "text/xml, text/html, " +
- "application/xhtml+xml, image/png, text/plain, */*;q=0.8";
-
- private static final String LOGTAG = "webkit";
-
- FrameLoader(LoadListener listener, WebSettings settings,
- String method, WebResourceResponse interceptResponse) {
- assert !JniUtil.useChromiumHttpStack();
-
- mListener = listener;
- mHeaders = null;
- mMethod = method;
- mCacheMode = WebSettings.LOAD_NORMAL;
- mSettings = settings;
- mInterceptResponse = interceptResponse;
- mUaprofHeader = mListener.getContext().getResources().getString(
- com.android.internal.R.string.config_useragentprofile_url, Build.MODEL);
- }
-
- public void setReferrer(String ref) {
- // only set referrer for http or https
- if (URLUtil.isNetworkUrl(ref)) mReferrer = ref;
- }
-
- public void setPostData(byte[] postData) {
- mPostData = postData;
- }
-
- public void setContentTypeForPost(String postContentType) {
- mContentType = postContentType;
- }
-
- public void setCacheMode(int cacheMode) {
- mCacheMode = cacheMode;
- }
-
- public void setHeaders(HashMap headers) {
- mHeaders = headers;
- }
-
- public LoadListener getLoadListener() {
- return mListener;
- }
-
- /**
- * Issues the load request.
- *
- * Return value does not indicate if the load was successful or not. It
- * simply indicates that the load request is reasonable.
- *
- * @return true if the load is reasonable.
- */
- public boolean executeLoad() {
- String url = mListener.url();
-
- // Process intercepted requests first as they could be any url.
- if (mInterceptResponse != null) {
- if (mListener.isSynchronous()) {
- mInterceptResponse.loader(mListener).load();
- } else {
- WebViewWorker.getHandler().obtainMessage(
- WebViewWorker.MSG_ADD_STREAMLOADER,
- mInterceptResponse.loader(mListener)).sendToTarget();
- }
- return true;
- } else if (URLUtil.isNetworkUrl(url)){
- if (mSettings.getBlockNetworkLoads()) {
- mListener.error(EventHandler.ERROR_BAD_URL,
- mListener.getContext().getString(
- com.android.internal.R.string.httpErrorBadUrl));
- return false;
- }
- // Make sure the host part of the url is correctly
- // encoded before sending the request
- if (!URLUtil.verifyURLEncoding(mListener.host())) {
- mListener.error(EventHandler.ERROR_BAD_URL,
- mListener.getContext().getString(
- com.android.internal.R.string.httpErrorBadUrl));
- return false;
- }
- mNetwork = Network.getInstance(mListener.getContext());
- if (mListener.isSynchronous()) {
- return handleHTTPLoad();
- }
- WebViewWorker.getHandler().obtainMessage(
- WebViewWorker.MSG_ADD_HTTPLOADER, this).sendToTarget();
- return true;
- } else if (handleLocalFile(url, mListener, mSettings)) {
- return true;
- }
- if (DebugFlags.FRAME_LOADER) {
- Log.v(LOGTAG, "FrameLoader.executeLoad: url protocol not supported:"
- + mListener.url());
- }
- mListener.error(EventHandler.ERROR_UNSUPPORTED_SCHEME,
- mListener.getContext().getText(
- com.android.internal.R.string.httpErrorUnsupportedScheme).toString());
- return false;
-
- }
-
- private static boolean handleLocalFile(String url, LoadListener loadListener,
- WebSettings settings) {
- assert !JniUtil.useChromiumHttpStack();
-
- // Attempt to decode the percent-encoded url before passing to the
- // local loaders.
- try {
- url = new String(URLUtil.decode(url.getBytes()));
- } catch (IllegalArgumentException e) {
- loadListener.error(EventHandler.ERROR_BAD_URL,
- loadListener.getContext().getString(
- com.android.internal.R.string.httpErrorBadUrl));
- // Return true here so we do not trigger an unsupported scheme
- // error.
- return true;
- }
- if (URLUtil.isAssetUrl(url)) {
- if (loadListener.isSynchronous()) {
- new FileLoader(url, loadListener, FileLoader.TYPE_ASSET,
- true).load();
- } else {
- // load asset in a separate thread as it involves IO
- WebViewWorker.getHandler().obtainMessage(
- WebViewWorker.MSG_ADD_STREAMLOADER,
- new FileLoader(url, loadListener, FileLoader.TYPE_ASSET,
- true)).sendToTarget();
- }
- return true;
- } else if (URLUtil.isResourceUrl(url)) {
- if (loadListener.isSynchronous()) {
- new FileLoader(url, loadListener, FileLoader.TYPE_RES,
- true).load();
- } else {
- // load resource in a separate thread as it involves IO
- WebViewWorker.getHandler().obtainMessage(
- WebViewWorker.MSG_ADD_STREAMLOADER,
- new FileLoader(url, loadListener, FileLoader.TYPE_RES,
- true)).sendToTarget();
- }
- return true;
- } else if (URLUtil.isFileUrl(url)) {
- if (loadListener.isSynchronous()) {
- new FileLoader(url, loadListener, FileLoader.TYPE_FILE,
- settings.getAllowFileAccess()).load();
- } else {
- // load file in a separate thread as it involves IO
- WebViewWorker.getHandler().obtainMessage(
- WebViewWorker.MSG_ADD_STREAMLOADER,
- new FileLoader(url, loadListener, FileLoader.TYPE_FILE,
- settings.getAllowFileAccess())).sendToTarget();
- }
- return true;
- } else if (settings.getAllowContentAccess() &&
- URLUtil.isContentUrl(url)) {
- // Send the raw url to the ContentLoader because it will do a
- // permission check and the url has to match.
- if (loadListener.isSynchronous()) {
- new ContentLoader(loadListener.url(), loadListener).load();
- } else {
- // load content in a separate thread as it involves IO
- WebViewWorker.getHandler().obtainMessage(
- WebViewWorker.MSG_ADD_STREAMLOADER,
- new ContentLoader(loadListener.url(), loadListener))
- .sendToTarget();
- }
- return true;
- } else if (URLUtil.isDataUrl(url)) {
- // load data in the current thread to reduce the latency
- new DataLoader(url, loadListener).load();
- return true;
- } else if (URLUtil.isAboutUrl(url)) {
- loadListener.data(mAboutBlank.getBytes(), mAboutBlank.length());
- loadListener.endData();
- return true;
- }
- return false;
- }
-
- boolean handleHTTPLoad() {
- if (mHeaders == null) {
- mHeaders = new HashMap<String, String>();
- }
- populateStaticHeaders();
- populateHeaders();
-
- // response was handled by Cache, don't issue HTTP request
- if (handleCache()) {
- // push the request data down to the LoadListener
- // as response from the cache could be a redirect
- // and we may need to initiate a network request if the cache
- // can't satisfy redirect URL
- mListener.setRequestData(mMethod, mHeaders, mPostData);
- return true;
- }
-
- if (DebugFlags.FRAME_LOADER) {
- Log.v(LOGTAG, "FrameLoader: http " + mMethod + " load for: "
- + mListener.url());
- }
-
- boolean ret = false;
- int error = EventHandler.ERROR_UNSUPPORTED_SCHEME;
-
- try {
- ret = mNetwork.requestURL(mMethod, mHeaders,
- mPostData, mListener);
- } catch (android.net.ParseException ex) {
- error = EventHandler.ERROR_BAD_URL;
- } catch (java.lang.RuntimeException ex) {
- /* probably an empty header set by javascript. We want
- the same result as bad URL */
- error = EventHandler.ERROR_BAD_URL;
- }
- if (!ret) {
- mListener.error(error, ErrorStrings.getString(error, mListener.getContext()));
- return false;
- }
- return true;
- }
-
- /*
- * This function is used by handleCache to
- * setup a load from the byte stream in a CacheResult.
- */
- private void startCacheLoad(CacheResult result) {
- if (DebugFlags.FRAME_LOADER) {
- Log.v(LOGTAG, "FrameLoader: loading from cache: "
- + mListener.url());
- }
- // Tell the Listener respond with the cache file
- CacheLoader cacheLoader =
- new CacheLoader(mListener, result);
- mListener.setCacheLoader(cacheLoader);
- if (mListener.isSynchronous()) {
- cacheLoader.load();
- } else {
- // Load the cached file in a separate thread
- WebViewWorker.getHandler().obtainMessage(
- WebViewWorker.MSG_ADD_STREAMLOADER, cacheLoader).sendToTarget();
- }
- }
-
- /*
- * This function is used by the handleHTTPLoad to setup the cache headers
- * correctly.
- * Returns true if the response was handled from the cache
- */
- private boolean handleCache() {
- switch (mCacheMode) {
- // This mode is normally used for a reload, it instructs the http
- // loader to not use the cached content.
- case WebSettings.LOAD_NO_CACHE:
- break;
-
-
- // This mode is used when the content should only be loaded from
- // the cache. If it is not there, then fail the load. This is used
- // to load POST content in a history navigation.
- case WebSettings.LOAD_CACHE_ONLY: {
- CacheResult result = CacheManager.getCacheFile(mListener.url(),
- mListener.postIdentifier(), null);
- if (result != null) {
- startCacheLoad(result);
- } else {
- // This happens if WebCore was first told that the POST
- // response was in the cache, then when we try to use it
- // it has gone.
- // Generate a file not found error
- int err = EventHandler.FILE_NOT_FOUND_ERROR;
- mListener.error(err,
- ErrorStrings.getString(err, mListener.getContext()));
- }
- return true;
- }
-
- // This mode is for when the user is doing a history navigation
- // in the browser and should returned cached content regardless
- // of it's state. If it is not in the cache, then go to the
- // network.
- case WebSettings.LOAD_CACHE_ELSE_NETWORK: {
- if (DebugFlags.FRAME_LOADER) {
- Log.v(LOGTAG, "FrameLoader: checking cache: "
- + mListener.url());
- }
- // Get the cache file name for the current URL, passing null for
- // the validation headers causes no validation to occur
- CacheResult result = CacheManager.getCacheFile(mListener.url(),
- mListener.postIdentifier(), null);
- if (result != null) {
- startCacheLoad(result);
- return true;
- }
- break;
- }
-
- // This is the default case, which is to check to see if the
- // content in the cache can be used. If it can be used, then
- // use it. If it needs revalidation then the relevant headers
- // are added to the request.
- default:
- case WebSettings.LOAD_NORMAL:
- return mListener.checkCache(mHeaders);
- }// end of switch
-
- return false;
- }
-
- /**
- * Add the static headers that don't change with each request.
- */
- private void populateStaticHeaders() {
- // Accept header should already be there as they are built by WebCore,
- // but in the case they are missing, add some.
- String accept = mHeaders.get("Accept");
- if (accept == null || accept.length() == 0) {
- mHeaders.put("Accept", HEADER_STR);
- }
- mHeaders.put("Accept-Charset", "utf-8, iso-8859-1, utf-16, *;q=0.7");
-
- String acceptLanguage = mSettings.getAcceptLanguage();
- if (acceptLanguage.length() > 0) {
- mHeaders.put("Accept-Language", acceptLanguage);
- }
-
- mHeaders.put("User-Agent", mSettings.getUserAgentString());
-
- // Set the x-wap-profile header
- if (mUaprofHeader != null && mUaprofHeader.length() > 0) {
- mHeaders.put("x-wap-profile", mUaprofHeader);
- }
- }
-
- /**
- * Add the content related headers. These headers contain user private data
- * and is not used when we are proxying an untrusted request.
- */
- private void populateHeaders() {
-
- if (mReferrer != null) mHeaders.put("Referer", mReferrer);
- if (mContentType != null) mHeaders.put(CONTENT_TYPE, mContentType);
-
- // if we have an active proxy and have proxy credentials, do pre-emptive
- // authentication to avoid an extra round-trip:
- if (mNetwork.isValidProxySet()) {
- String username;
- String password;
- /* The proxy credentials can be set in the Network thread */
- synchronized (mNetwork) {
- username = mNetwork.getProxyUsername();
- password = mNetwork.getProxyPassword();
- }
- if (username != null && password != null) {
- // we collect credentials ONLY if the proxy scheme is BASIC!!!
- String proxyHeader = RequestHandle.authorizationHeader(true);
- mHeaders.put(proxyHeader,
- "Basic " + RequestHandle.computeBasicAuthResponse(
- username, password));
- }
- }
-
- // Set cookie header
- String cookie = CookieManager.getInstance().getCookie(
- mListener.getWebAddress());
- if (cookie != null && cookie.length() > 0) {
- mHeaders.put("Cookie", cookie);
- }
- }
-}
diff --git a/core/java/android/webkit/HttpAuthHandlerImpl.java b/core/java/android/webkit/HttpAuthHandlerImpl.java
deleted file mode 100644
index 01e8eb8..0000000
--- a/core/java/android/webkit/HttpAuthHandlerImpl.java
+++ /dev/null
@@ -1,279 +0,0 @@
-/*
- * 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 android.webkit;
-
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.util.Log;
-
-import java.util.ListIterator;
-import java.util.LinkedList;
-
-/**
- * HttpAuthHandler implementation is used only by the Android Java HTTP stack.
- * <p>
- * This class is not needed when we're using the Chromium HTTP stack.
- */
-class HttpAuthHandlerImpl extends HttpAuthHandler {
- /*
- * It is important that the handler is in Network, because we want to share
- * it accross multiple loaders and windows (like our subwindow and the main
- * window).
- */
-
- private static final String LOGTAG = "network";
-
- /**
- * Network.
- */
- private Network mNetwork;
-
- /**
- * Loader queue.
- */
- private LinkedList<LoadListener> mLoaderQueue;
-
-
- // Message id for handling the user response
- private static final int AUTH_PROCEED = 100;
- private static final int AUTH_CANCEL = 200;
-
- // Use to synchronize when making synchronous calls to
- // onReceivedHttpAuthRequest(). We can't use a single Boolean object for
- // both the lock and the state, because Boolean is immutable.
- Object mRequestInFlightLock = new Object();
- boolean mRequestInFlight;
- String mUsername;
- String mPassword;
-
- /**
- * Creates a new HTTP authentication handler with an empty
- * loader queue
- *
- * @param network The parent network object
- */
- /* package */ HttpAuthHandlerImpl(Network network) {
- mNetwork = network;
- mLoaderQueue = new LinkedList<LoadListener>();
- }
-
-
- @Override
- public void handleMessage(Message msg) {
- LoadListener loader = null;
- synchronized (mLoaderQueue) {
- loader = mLoaderQueue.poll();
- }
- assert(loader.isSynchronous() == false);
-
- switch (msg.what) {
- case AUTH_PROCEED:
- String username = msg.getData().getString("username");
- String password = msg.getData().getString("password");
-
- loader.handleAuthResponse(username, password);
- break;
-
- case AUTH_CANCEL:
- loader.handleAuthResponse(null, null);
- break;
- }
-
- processNextLoader();
- }
-
- /**
- * Helper method used to unblock handleAuthRequest(), which in the case of a
- * synchronous request will wait for proxy.onReceivedHttpAuthRequest() to
- * call back to either proceed() or cancel().
- *
- * @param username The username to use for authentication
- * @param password The password to use for authentication
- * @return True if the request is synchronous and handleAuthRequest() has
- * been unblocked
- */
- private boolean handleResponseForSynchronousRequest(String username, String password) {
- LoadListener loader = null;
- synchronized (mLoaderQueue) {
- loader = mLoaderQueue.peek();
- }
- if (loader.isSynchronous()) {
- mUsername = username;
- mPassword = password;
- return true;
- }
- return false;
- }
-
- private void signalRequestComplete() {
- synchronized (mRequestInFlightLock) {
- assert(mRequestInFlight);
- mRequestInFlight = false;
- mRequestInFlightLock.notify();
- }
- }
-
- /**
- * Proceed with the authorization with the given credentials
- *
- * May be called on the UI thread, rather than the WebCore thread.
- *
- * @param username The username to use for authentication
- * @param password The password to use for authentication
- */
- public void proceed(String username, String password) {
- if (handleResponseForSynchronousRequest(username, password)) {
- signalRequestComplete();
- return;
- }
- Message msg = obtainMessage(AUTH_PROCEED);
- msg.getData().putString("username", username);
- msg.getData().putString("password", password);
- sendMessage(msg);
- signalRequestComplete();
- }
-
- /**
- * Cancel the authorization request
- *
- * May be called on the UI thread, rather than the WebCore thread.
- *
- */
- public void cancel() {
- if (handleResponseForSynchronousRequest(null, null)) {
- signalRequestComplete();
- return;
- }
- sendMessage(obtainMessage(AUTH_CANCEL));
- signalRequestComplete();
- }
-
- /**
- * @return True if we can use user credentials on record
- * (ie, if we did not fail trying to use them last time)
- */
- public boolean useHttpAuthUsernamePassword() {
- LoadListener loader = null;
- synchronized (mLoaderQueue) {
- loader = mLoaderQueue.peek();
- }
- if (loader != null) {
- return !loader.authCredentialsInvalid();
- }
-
- return false;
- }
-
- /**
- * Enqueues the loader, if the loader is the only element
- * in the queue, starts processing the loader
- *
- * @param loader The loader that resulted in this http
- * authentication request
- */
- /* package */ void handleAuthRequest(LoadListener loader) {
- // The call to proxy.onReceivedHttpAuthRequest() may be asynchronous. If
- // the request is synchronous, we must block here until we have a
- // response.
- if (loader.isSynchronous()) {
- // If there's a request in flight, wait for it to complete. The
- // response will queue a message on this thread.
- waitForRequestToComplete();
- // Make a request to the proxy for this request, jumping the queue.
- // We use the queue so that the loader is present in
- // useHttpAuthUsernamePassword().
- synchronized (mLoaderQueue) {
- mLoaderQueue.addFirst(loader);
- }
- processNextLoader();
- // Wait for this request to complete.
- waitForRequestToComplete();
- // Pop the loader from the queue.
- synchronized (mLoaderQueue) {
- assert(mLoaderQueue.peek() == loader);
- mLoaderQueue.poll();
- }
- // Call back.
- loader.handleAuthResponse(mUsername, mPassword);
- // The message queued by the response from the last asynchronous
- // request, if present, will start the next request.
- return;
- }
-
- boolean processNext = false;
-
- synchronized (mLoaderQueue) {
- mLoaderQueue.offer(loader);
- processNext =
- (mLoaderQueue.size() == 1);
- }
-
- if (processNext) {
- processNextLoader();
- }
- }
-
- /**
- * Wait for the request in flight, if any, to complete
- */
- private void waitForRequestToComplete() {
- synchronized (mRequestInFlightLock) {
- while (mRequestInFlight) {
- try {
- mRequestInFlightLock.wait();
- } catch(InterruptedException e) {
- Log.e(LOGTAG, "Interrupted while waiting for request to complete");
- }
- }
- }
- }
-
- /**
- * Process the next loader in the queue (helper method)
- */
- private void processNextLoader() {
- LoadListener loader = null;
- synchronized (mLoaderQueue) {
- loader = mLoaderQueue.peek();
- }
- if (loader != null) {
- synchronized (mRequestInFlightLock) {
- assert(mRequestInFlight == false);
- mRequestInFlight = true;
- }
-
- CallbackProxy proxy = loader.getFrame().getCallbackProxy();
-
- String hostname = loader.proxyAuthenticate() ?
- mNetwork.getProxyHostname() : loader.host();
-
- String realm = loader.realm();
-
- proxy.onReceivedHttpAuthRequest(this, hostname, realm);
- }
- }
-
- /**
- * Informs the WebView of a new set of credentials.
- */
- public static void onReceivedCredentials(LoadListener loader,
- String host, String realm, String username, String password) {
- CallbackProxy proxy = loader.getFrame().getCallbackProxy();
- proxy.onReceivedHttpAuthCredentials(host, realm, username, password);
- }
-}
diff --git a/core/java/android/webkit/JniUtil.java b/core/java/android/webkit/JniUtil.java
index 7b44938..343d34a 100644
--- a/core/java/android/webkit/JniUtil.java
+++ b/core/java/android/webkit/JniUtil.java
@@ -37,7 +37,6 @@
// Used by the Chromium HTTP stack.
private static String sDatabaseDirectory;
private static String sCacheDirectory;
- private static Boolean sUseChromiumHttpStack;
private static Context sContext;
private static void checkInitialized() {
@@ -111,10 +110,9 @@
// content://
if (url.startsWith(ANDROID_CONTENT)) {
try {
- // Strip off mimetype, for compatibility with ContentLoader.java
- // If we don't do this, we can fail to load Gmail attachments,
- // because the URL being loaded doesn't exactly match the URL we
- // have permission to read.
+ // Strip off MIME type. If we don't do this, we can fail to
+ // load Gmail attachments, because the URL being loaded doesn't
+ // exactly match the URL we have permission to read.
int mimeIndex = url.lastIndexOf('?');
if (mimeIndex != -1) {
url = url.substring(0, mimeIndex);
@@ -152,6 +150,7 @@
if (url.startsWith(ANDROID_CONTENT)) {
try {
// Strip off mimetype, for compatibility with ContentLoader.java
+ // (used with Android HTTP stack, now removed).
// If we don't do this, we can fail to load Gmail attachments,
// because the URL being loaded doesn't exactly match the URL we
// have permission to read.
@@ -170,19 +169,6 @@
}
}
- /**
- * Returns true if we're using the Chromium HTTP stack.
- *
- * TODO: Remove this if/when we permanently switch to the Chromium HTTP stack
- * http:/b/3118772
- */
- static boolean useChromiumHttpStack() {
- if (sUseChromiumHttpStack == null) {
- sUseChromiumHttpStack = nativeUseChromiumHttpStack();
- }
- return sUseChromiumHttpStack;
- }
-
private static synchronized String getAutofillQueryUrl() {
checkInitialized();
// If the device has not checked in it won't have pulled down the system setting for the
@@ -200,7 +186,4 @@
long leftToAllocate = memInfo.availMem - memInfo.threshold;
return !memInfo.lowMemory && bytesRequested < leftToAllocate;
}
-
-
- private static native boolean nativeUseChromiumHttpStack();
}
diff --git a/core/java/android/webkit/LoadListener.java b/core/java/android/webkit/LoadListener.java
deleted file mode 100644
index 4d7ade51..0000000
--- a/core/java/android/webkit/LoadListener.java
+++ /dev/null
@@ -1,1685 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.webkit;
-
-import android.content.ActivityNotFoundException;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.net.ParseException;
-import android.net.Uri;
-import android.net.WebAddress;
-import android.net.http.EventHandler;
-import android.net.http.Headers;
-import android.net.http.HttpAuthHeader;
-import android.net.http.RequestHandle;
-import android.net.http.SslCertificate;
-import android.net.http.SslError;
-
-import android.os.Handler;
-import android.os.Message;
-import android.util.Log;
-import android.webkit.CacheManager.CacheResult;
-import android.webkit.JniUtil;
-
-import com.android.internal.R;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Vector;
-import java.util.regex.Pattern;
-import java.util.regex.Matcher;
-
-class LoadListener extends Handler implements EventHandler {
-
- private static final String LOGTAG = "webkit";
-
- // Messages used internally to communicate state between the
- // Network thread and the WebCore thread.
- private static final int MSG_CONTENT_HEADERS = 100;
- private static final int MSG_CONTENT_DATA = 110;
- private static final int MSG_CONTENT_FINISHED = 120;
- private static final int MSG_CONTENT_ERROR = 130;
- private static final int MSG_LOCATION_CHANGED = 140;
- private static final int MSG_LOCATION_CHANGED_REQUEST = 150;
- private static final int MSG_STATUS = 160;
- private static final int MSG_SSL_CERTIFICATE = 170;
- private static final int MSG_SSL_ERROR = 180;
-
- // Standard HTTP status codes in a more representative format
- private static final int HTTP_OK = 200;
- private static final int HTTP_PARTIAL_CONTENT = 206;
- private static final int HTTP_MOVED_PERMANENTLY = 301;
- private static final int HTTP_FOUND = 302;
- private static final int HTTP_SEE_OTHER = 303;
- private static final int HTTP_NOT_MODIFIED = 304;
- private static final int HTTP_TEMPORARY_REDIRECT = 307;
- private static final int HTTP_AUTH = 401;
- private static final int HTTP_NOT_FOUND = 404;
- private static final int HTTP_PROXY_AUTH = 407;
-
- private static int sNativeLoaderCount;
-
- private final ByteArrayBuilder mDataBuilder = new ByteArrayBuilder();
-
- private String mUrl;
- private WebAddress mUri;
- private boolean mPermanent;
- private String mOriginalUrl;
- private Context mContext;
- private BrowserFrame mBrowserFrame;
- private int mNativeLoader;
- private String mMimeType;
- private String mEncoding;
- private String mTransferEncoding;
- private int mStatusCode;
- private String mStatusText;
- public long mContentLength; // Content length of the incoming data
- private boolean mCancelled; // The request has been cancelled.
- private boolean mAuthFailed; // indicates that the prev. auth failed
- private CacheLoader mCacheLoader;
- private boolean mFromCache = false;
- private HttpAuthHeader mAuthHeader;
- private int mErrorID = OK;
- private String mErrorDescription;
- private SslError mSslError;
- private RequestHandle mRequestHandle;
- private RequestHandle mSslErrorRequestHandle;
- private long mPostIdentifier;
- private boolean mSetNativeResponse;
-
- // Request data. It is only valid when we are doing a load from the
- // cache. It is needed if the cache returns a redirect
- private String mMethod;
- private Map<String, String> mRequestHeaders;
- private byte[] mPostData;
- // Flag to indicate that this load is synchronous.
- private boolean mSynchronous;
- private Vector<Message> mMessageQueue;
-
- // Does this loader correspond to the main-frame top-level page?
- private boolean mIsMainPageLoader;
- // Does this loader correspond to the main content (as opposed to a supporting resource)
- private final boolean mIsMainResourceLoader;
- private final boolean mUserGesture;
-
- private Headers mHeaders;
-
- private final String mUsername;
- private final String mPassword;
-
- // =========================================================================
- // Public functions
- // =========================================================================
-
- public static LoadListener getLoadListener(Context context,
- BrowserFrame frame, String url, int nativeLoader,
- boolean synchronous, boolean isMainPageLoader,
- boolean isMainResource, boolean userGesture, long postIdentifier,
- String username, String password) {
-
- sNativeLoaderCount += 1;
- return new LoadListener(context, frame, url, nativeLoader, synchronous,
- isMainPageLoader, isMainResource, userGesture, postIdentifier,
- username, password);
- }
-
- public static int getNativeLoaderCount() {
- return sNativeLoaderCount;
- }
-
- LoadListener(Context context, BrowserFrame frame, String url,
- int nativeLoader, boolean synchronous, boolean isMainPageLoader,
- boolean isMainResource, boolean userGesture, long postIdentifier,
- String username, String password) {
- assert !JniUtil.useChromiumHttpStack();
-
- if (DebugFlags.LOAD_LISTENER) {
- Log.v(LOGTAG, "LoadListener constructor url=" + url);
- }
- mContext = context;
- mBrowserFrame = frame;
- setUrl(url);
- mNativeLoader = nativeLoader;
- mSynchronous = synchronous;
- if (synchronous) {
- mMessageQueue = new Vector<Message>();
- }
- mIsMainPageLoader = isMainPageLoader;
- mIsMainResourceLoader = isMainResource;
- mUserGesture = userGesture;
- mPostIdentifier = postIdentifier;
- mUsername = username;
- mPassword = password;
- }
-
- /**
- * We keep a count of refs to the nativeLoader so we do not create
- * so many LoadListeners that the GREFs blow up
- */
- private void clearNativeLoader() {
- sNativeLoaderCount -= 1;
- mNativeLoader = 0;
- mSetNativeResponse = false;
- }
-
- /*
- * This message handler is to facilitate communication between the network
- * thread and the browser thread.
- */
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_CONTENT_HEADERS:
- /*
- * This message is sent when the LoadListener has headers
- * available. The headers are sent onto WebCore to see what we
- * should do with them.
- */
- handleHeaders((Headers) msg.obj);
- break;
-
- case MSG_CONTENT_DATA:
- /*
- * This message is sent when the LoadListener has data available
- * in it's data buffer. This data buffer could be filled from a
- * file (this thread) or from http (Network thread).
- */
- if (mNativeLoader != 0 && !ignoreCallbacks()) {
- commitLoad();
- }
- break;
-
- case MSG_CONTENT_FINISHED:
- /*
- * This message is sent when the LoadListener knows that the
- * load is finished. This message is not sent in the case of an
- * error.
- *
- */
- handleEndData();
- break;
-
- case MSG_CONTENT_ERROR:
- /*
- * This message is sent when a load error has occured. The
- * LoadListener will clean itself up.
- */
- handleError(msg.arg1, (String) msg.obj);
- break;
-
- case MSG_LOCATION_CHANGED:
- /*
- * This message is sent from LoadListener.endData to inform the
- * browser activity that the location of the top level page
- * changed.
- */
- doRedirect();
- break;
-
- case MSG_LOCATION_CHANGED_REQUEST:
- /*
- * This message is sent from endData on receipt of a 307
- * Temporary Redirect in response to a POST -- the user must
- * confirm whether to continue loading. If the user says Yes,
- * we simply call MSG_LOCATION_CHANGED. If the user says No,
- * we call MSG_CONTENT_FINISHED.
- */
- Message contMsg = obtainMessage(MSG_LOCATION_CHANGED);
- Message stopMsg = obtainMessage(MSG_CONTENT_FINISHED);
- mBrowserFrame.getCallbackProxy().onFormResubmission(
- stopMsg, contMsg);
- break;
-
- case MSG_STATUS:
- /*
- * This message is sent from the network thread when the http
- * stack has received the status response from the server.
- */
- HashMap status = (HashMap) msg.obj;
- handleStatus(((Integer) status.get("major")).intValue(),
- ((Integer) status.get("minor")).intValue(),
- ((Integer) status.get("code")).intValue(),
- (String) status.get("reason"));
- break;
-
- case MSG_SSL_CERTIFICATE:
- /*
- * This message is sent when the network thread receives a ssl
- * certificate.
- */
- handleCertificate((SslCertificate) msg.obj);
- break;
-
- case MSG_SSL_ERROR:
- /*
- * This message is sent when the network thread encounters a
- * ssl error.
- */
- handleSslError((SslError) msg.obj);
- break;
- }
- }
-
- /**
- * @return The loader's BrowserFrame.
- */
- BrowserFrame getFrame() {
- return mBrowserFrame;
- }
-
- Context getContext() {
- return mContext;
- }
-
- /* package */ boolean isSynchronous() {
- return mSynchronous;
- }
-
- /**
- * @return True iff the load has been cancelled
- */
- public boolean cancelled() {
- return mCancelled;
- }
-
- /**
- * Parse the headers sent from the server.
- * @param headers gives up the HeaderGroup
- * IMPORTANT: as this is called from network thread, can't call native
- * directly
- */
- public void headers(Headers headers) {
- if (DebugFlags.LOAD_LISTENER) Log.v(LOGTAG, "LoadListener.headers");
- // call db (setCookie) in the non-WebCore thread
- if (mCancelled) return;
- ArrayList<String> cookies = headers.getSetCookie();
- for (int i = 0; i < cookies.size(); ++i) {
- CookieManager.getInstance().setCookie(mUri, cookies.get(i));
- }
- sendMessageInternal(obtainMessage(MSG_CONTENT_HEADERS, headers));
- }
-
- // This is the same regex that DOMImplementation uses to check for xml
- // content. Use this to check if another Activity wants to handle the
- // content before giving it to webkit.
- private static final String XML_MIME_TYPE =
- "^[\\w_\\-+~!$\\^{}|.%'`#&*]+/" +
- "[\\w_\\-+~!$\\^{}|.%'`#&*]+\\+xml$";
-
- // Does the header parsing work on the WebCore thread.
- private void handleHeaders(Headers headers) {
- if (mCancelled) return;
-
- // Note: the headers we care in LoadListeners, like
- // content-type/content-length, should not be updated for partial
- // content. Just skip here and go ahead with adding data.
- if (mStatusCode == HTTP_PARTIAL_CONTENT) {
- // we don't support cache for partial content yet
- WebViewWorker.getHandler().obtainMessage(
- WebViewWorker.MSG_REMOVE_CACHE, this).sendToTarget();
- return;
- }
-
- mHeaders = headers;
-
- long contentLength = headers.getContentLength();
- if (contentLength != Headers.NO_CONTENT_LENGTH) {
- mContentLength = contentLength;
- } else {
- mContentLength = 0;
- }
-
- String contentType = headers.getContentType();
- if (contentType != null) {
- parseContentTypeHeader(contentType);
- mMimeType = MimeTypeMap.getSingleton().remapGenericMimeType(
- mMimeType, mUrl, headers.getContentDisposition());
- } else {
- /* Often when servers respond with 304 Not Modified or a
- Redirect, then they don't specify a MIMEType. When this
- occurs, the function below is called. In the case of
- 304 Not Modified, the cached headers are used rather
- than the headers that are returned from the server. */
- guessMimeType();
- }
- // At this point, mMimeType has been set to non-null.
- if (mIsMainPageLoader && mIsMainResourceLoader && mUserGesture &&
- Pattern.matches(XML_MIME_TYPE, mMimeType) &&
- !mMimeType.equalsIgnoreCase("application/xhtml+xml")) {
- Intent i = new Intent(Intent.ACTION_VIEW);
- i.setDataAndType(Uri.parse(url()), mMimeType);
- ResolveInfo info = mContext.getPackageManager().resolveActivity(i,
- PackageManager.MATCH_DEFAULT_ONLY);
- if (info != null && !mContext.getPackageName().equals(
- info.activityInfo.packageName)) {
- // someone (other than the current app) knows how to
- // handle this mime type.
- try {
- mContext.startActivity(i);
- mBrowserFrame.stopLoading();
- return;
- } catch (ActivityNotFoundException ex) {
- // continue loading internally.
- }
- }
- }
-
- // is it an authentication request?
- boolean mustAuthenticate = (mStatusCode == HTTP_AUTH ||
- mStatusCode == HTTP_PROXY_AUTH);
- // is it a proxy authentication request?
- boolean isProxyAuthRequest = (mStatusCode == HTTP_PROXY_AUTH);
- // is this authentication request due to a failed attempt to
- // authenticate ealier?
- mAuthFailed = false;
-
- // if we tried to authenticate ourselves last time
- if (mAuthHeader != null) {
- // we failed, if we must authenticate again now and
- // we have a proxy-ness match
- mAuthFailed = (mustAuthenticate &&
- isProxyAuthRequest == mAuthHeader.isProxy());
-
- // if we did NOT fail and last authentication request was a
- // proxy-authentication request
- if (!mAuthFailed && mAuthHeader.isProxy()) {
- Network network = Network.getInstance(mContext);
- // if we have a valid proxy set
- if (network.isValidProxySet()) {
- /* The proxy credentials can be read in the WebCore thread
- */
- synchronized (network) {
- // save authentication credentials for pre-emptive proxy
- // authentication
- network.setProxyUsername(mAuthHeader.getUsername());
- network.setProxyPassword(mAuthHeader.getPassword());
- }
- }
- }
- }
-
- // it is only here that we can reset the last mAuthHeader object
- // (if existed) and start a new one!!!
- mAuthHeader = null;
- if (mustAuthenticate) {
- if (mStatusCode == HTTP_AUTH) {
- mAuthHeader = parseAuthHeader(
- headers.getWwwAuthenticate());
- } else {
- mAuthHeader = parseAuthHeader(
- headers.getProxyAuthenticate());
- // if successfully parsed the header
- if (mAuthHeader != null) {
- // mark the auth-header object as a proxy
- mAuthHeader.setProxy();
- }
- }
- }
-
- // Only create a cache file if the server has responded positively.
- if ((mStatusCode == HTTP_OK ||
- mStatusCode == HTTP_FOUND ||
- mStatusCode == HTTP_MOVED_PERMANENTLY ||
- mStatusCode == HTTP_TEMPORARY_REDIRECT) &&
- mNativeLoader != 0) {
- // for POST request, only cache the result if there is an identifier
- // associated with it. postUrl() or form submission should set the
- // identifier while XHR POST doesn't.
- if (!mFromCache && mRequestHandle != null
- && (!mRequestHandle.getMethod().equals("POST")
- || mPostIdentifier != 0)) {
- WebViewWorker.CacheCreateData data = new WebViewWorker.CacheCreateData();
- data.mListener = this;
- data.mUrl = mUrl;
- data.mMimeType = mMimeType;
- data.mStatusCode = mStatusCode;
- data.mPostId = mPostIdentifier;
- data.mHeaders = headers;
- WebViewWorker.getHandler().obtainMessage(
- WebViewWorker.MSG_CREATE_CACHE, data).sendToTarget();
- }
- WebViewWorker.CacheEncoding ce = new WebViewWorker.CacheEncoding();
- ce.mEncoding = mEncoding;
- ce.mListener = this;
- WebViewWorker.getHandler().obtainMessage(
- WebViewWorker.MSG_UPDATE_CACHE_ENCODING, ce).sendToTarget();
- }
- commitHeadersCheckRedirect();
- }
-
- /**
- * @return True iff this loader is in the proxy-authenticate state.
- */
- boolean proxyAuthenticate() {
- if (mAuthHeader != null) {
- return mAuthHeader.isProxy();
- }
-
- return false;
- }
-
- /**
- * Report the status of the response.
- * TODO: Comments about each parameter.
- * IMPORTANT: as this is called from network thread, can't call native
- * directly
- */
- public void status(int majorVersion, int minorVersion,
- int code, /* Status-Code value */ String reasonPhrase) {
- if (DebugFlags.LOAD_LISTENER) {
- Log.v(LOGTAG, "LoadListener: from: " + mUrl
- + " major: " + majorVersion
- + " minor: " + minorVersion
- + " code: " + code
- + " reason: " + reasonPhrase);
- }
- HashMap status = new HashMap();
- status.put("major", majorVersion);
- status.put("minor", minorVersion);
- status.put("code", code);
- status.put("reason", reasonPhrase);
- // New status means new data. Clear the old.
- mDataBuilder.clear();
- mMimeType = "";
- mEncoding = "";
- mTransferEncoding = "";
- sendMessageInternal(obtainMessage(MSG_STATUS, status));
- }
-
- // Handle the status callback on the WebCore thread.
- private void handleStatus(int major, int minor, int code, String reason) {
- if (mCancelled) return;
-
- mStatusCode = code;
- mStatusText = reason;
- mPermanent = false;
- }
-
- /**
- * Implementation of certificate handler for EventHandler. Called
- * before a resource is requested. In this context, can be called
- * multiple times if we have redirects
- *
- * IMPORTANT: as this is called from network thread, can't call
- * native directly
- *
- * @param certificate The SSL certifcate or null if the request
- * was not secure
- */
- public void certificate(SslCertificate certificate) {
- if (DebugFlags.LOAD_LISTENER) {
- Log.v(LOGTAG, "LoadListener.certificate: " + certificate);
- }
- sendMessageInternal(obtainMessage(MSG_SSL_CERTIFICATE, certificate));
- }
-
- // Handle the certificate on the WebCore thread.
- private void handleCertificate(SslCertificate certificate) {
- // if this is main resource of the top frame
- if (mIsMainPageLoader && mIsMainResourceLoader) {
- // update the browser frame with certificate
- mBrowserFrame.certificate(certificate);
- }
- }
-
- /**
- * Implementation of error handler for EventHandler.
- * Subclasses should call this method to have error fields set.
- * @param id The error id described by EventHandler.
- * @param description A string description of the error.
- * IMPORTANT: as this is called from network thread, can't call native
- * directly
- */
- public void error(int id, String description) {
- if (DebugFlags.LOAD_LISTENER) {
- Log.v(LOGTAG, "LoadListener.error url:" +
- url() + " id:" + id + " description:" + description);
- }
- sendMessageInternal(obtainMessage(MSG_CONTENT_ERROR, id, 0, description));
- }
-
- // Handle the error on the WebCore thread.
- private void handleError(int id, String description) {
- mErrorID = id;
- mErrorDescription = description;
- detachRequestHandle();
- notifyError();
- tearDown();
- }
-
- /**
- * Add data to the internal collection of data. This function is used by
- * the data: scheme, about: scheme and http/https schemes.
- * @param data A byte array containing the content.
- * @param length The length of data.
- * IMPORTANT: as this is called from network thread, can't call native
- * directly
- * XXX: Unlike the other network thread methods, this method can do the
- * work of decoding the data and appending it to the data builder.
- */
- public void data(byte[] data, int length) {
- if (DebugFlags.LOAD_LISTENER) {
- Log.v(LOGTAG, "LoadListener.data(): url: " + url());
- }
-
- // The reason isEmpty() and append() need to synchronized together is
- // because it is possible for getFirstChunk() to be called multiple
- // times between isEmpty() and append(). This could cause commitLoad()
- // to finish before processing the newly appended data and no message
- // will be sent.
- boolean sendMessage = false;
- synchronized (mDataBuilder) {
- sendMessage = mDataBuilder.isEmpty();
- mDataBuilder.append(data, 0, length);
- }
- if (sendMessage) {
- // Send a message whenever data comes in after a write to WebCore
- sendMessageInternal(obtainMessage(MSG_CONTENT_DATA));
- }
- }
-
- /**
- * Event handler's endData call. Send a message to the handler notifying
- * them that the data has finished.
- * IMPORTANT: as this is called from network thread, can't call native
- * directly
- */
- public void endData() {
- if (DebugFlags.LOAD_LISTENER) {
- Log.v(LOGTAG, "LoadListener.endData(): url: " + url());
- }
- sendMessageInternal(obtainMessage(MSG_CONTENT_FINISHED));
- }
-
- // Handle the end of data.
- private void handleEndData() {
- if (mCancelled) return;
-
- switch (mStatusCode) {
- case HTTP_MOVED_PERMANENTLY:
- // 301 - permanent redirect
- mPermanent = true;
- case HTTP_FOUND:
- case HTTP_SEE_OTHER:
- case HTTP_TEMPORARY_REDIRECT:
- // 301, 302, 303, and 307 - redirect
- if (mStatusCode == HTTP_TEMPORARY_REDIRECT) {
- if (mRequestHandle != null &&
- mRequestHandle.getMethod().equals("POST")) {
- sendMessageInternal(obtainMessage(
- MSG_LOCATION_CHANGED_REQUEST));
- } else if (mMethod != null && mMethod.equals("POST")) {
- sendMessageInternal(obtainMessage(
- MSG_LOCATION_CHANGED_REQUEST));
- } else {
- sendMessageInternal(obtainMessage(MSG_LOCATION_CHANGED));
- }
- } else {
- sendMessageInternal(obtainMessage(MSG_LOCATION_CHANGED));
- }
- return;
-
- case HTTP_AUTH:
- case HTTP_PROXY_AUTH:
- // According to rfc2616, the response for HTTP_AUTH must include
- // WWW-Authenticate header field and the response for
- // HTTP_PROXY_AUTH must include Proxy-Authenticate header field.
- if (mAuthHeader != null &&
- (Network.getInstance(mContext).isValidProxySet() ||
- !mAuthHeader.isProxy())) {
- // If this is the first attempt to authenticate, try again with the username and
- // password supplied in the URL, if present.
- if (!mAuthFailed && mUsername != null && mPassword != null) {
- String host = mAuthHeader.isProxy() ?
- Network.getInstance(mContext).getProxyHostname() :
- mUri.getHost();
- HttpAuthHandlerImpl.onReceivedCredentials(this, host,
- mAuthHeader.getRealm(), mUsername, mPassword);
- makeAuthResponse(mUsername, mPassword);
- } else {
- Network.getInstance(mContext).handleAuthRequest(this);
- }
- return;
- }
- break; // use default
-
- case HTTP_NOT_MODIFIED:
- // Server could send back NOT_MODIFIED even if we didn't
- // ask for it, so make sure we have a valid CacheLoader
- // before calling it.
- if (mCacheLoader != null) {
- if (isSynchronous()) {
- mCacheLoader.load();
- } else {
- // Load the cached file in a separate thread
- WebViewWorker.getHandler().obtainMessage(
- WebViewWorker.MSG_ADD_STREAMLOADER, mCacheLoader)
- .sendToTarget();
- }
- mFromCache = true;
- if (DebugFlags.LOAD_LISTENER) {
- Log.v(LOGTAG, "LoadListener cache load url=" + url());
- }
- return;
- }
- break; // use default
-
- case HTTP_NOT_FOUND:
- // Not an error, the server can send back content.
- default:
- break;
- }
- detachRequestHandle();
- tearDown();
- }
-
- /* This method is called from CacheLoader when the initial request is
- * serviced by the Cache. */
- /* package */ void setCacheLoader(CacheLoader c) {
- mCacheLoader = c;
- mFromCache = true;
- }
-
- /**
- * Check the cache for the current URL, and load it if it is valid.
- *
- * @param headers for the request
- * @return true if cached response is used.
- */
- boolean checkCache(Map<String, String> headers) {
- // Get the cache file name for the current URL
- CacheResult result = CacheManager.getCacheFile(url(), mPostIdentifier,
- headers);
-
- // Go ahead and set the cache loader to null in case the result is
- // null.
- mCacheLoader = null;
- // reset the flag
- mFromCache = false;
-
- if (result != null) {
- // The contents of the cache may need to be revalidated so just
- // remember the cache loader in the case that the server responds
- // positively to the cached content. This is also used to detect if
- // a redirect came from the cache.
- mCacheLoader = new CacheLoader(this, result);
-
- // If I got a cachedUrl and the revalidation header was not
- // added, then the cached content valid, we should use it.
- if (!headers.containsKey(
- CacheManager.HEADER_KEY_IFNONEMATCH) &&
- !headers.containsKey(
- CacheManager.HEADER_KEY_IFMODIFIEDSINCE)) {
- if (DebugFlags.LOAD_LISTENER) {
- Log.v(LOGTAG, "FrameLoader: HTTP URL in cache " +
- "and usable: " + url());
- }
- if (isSynchronous()) {
- mCacheLoader.load();
- } else {
- // Load the cached file in a separate thread
- WebViewWorker.getHandler().obtainMessage(
- WebViewWorker.MSG_ADD_STREAMLOADER, mCacheLoader)
- .sendToTarget();
- }
- mFromCache = true;
- return true;
- }
- }
- return false;
- }
-
- /**
- * SSL certificate error callback. Handles SSL error(s) on the way up
- * to the user.
- * IMPORTANT: as this is called from network thread, can't call native
- * directly
- */
- public boolean handleSslErrorRequest(SslError error) {
- if (DebugFlags.LOAD_LISTENER) {
- Log.v(LOGTAG,
- "LoadListener.handleSslErrorRequest(): url:" + url() +
- " primary error: " + error.getPrimaryError() +
- " certificate: " + error.getCertificate());
- }
- // Check the cached preference table before sending a message. This
- // will prevent waiting for an already available answer.
- if (Network.getInstance(mContext).checkSslPrefTable(this, error)) {
- return true;
- }
- // Do not post a message for a synchronous request. This will cause a
- // deadlock. Just bail on the request.
- if (isSynchronous()) {
- mRequestHandle.handleSslErrorResponse(false);
- return true;
- }
- sendMessageInternal(obtainMessage(MSG_SSL_ERROR, error));
- // if it has been canceled, return false so that the network thread
- // won't be blocked. If it is not canceled, save the mRequestHandle
- // so that if it is canceled when MSG_SSL_ERROR is handled, we can
- // still call handleSslErrorResponse which will call restartConnection
- // to unblock the network thread.
- if (!mCancelled) {
- mSslErrorRequestHandle = mRequestHandle;
- }
- return !mCancelled;
- }
-
- // Handle the ssl error on the WebCore thread.
- private void handleSslError(SslError error) {
- if (!mCancelled) {
- mSslError = error;
- Network.getInstance(mContext).handleSslErrorRequest(this);
- } else if (mSslErrorRequestHandle != null) {
- mSslErrorRequestHandle.handleSslErrorResponse(true);
- }
- mSslErrorRequestHandle = null;
- }
-
- /**
- * @return HTTP authentication realm or null if none.
- */
- String realm() {
- if (mAuthHeader == null) {
- return null;
- } else {
- return mAuthHeader.getRealm();
- }
- }
-
- /**
- * Returns true iff an HTTP authentication problem has
- * occured (credentials invalid).
- */
- boolean authCredentialsInvalid() {
- // if it is digest and the nonce is stale, we just
- // resubmit with a new nonce
- return (mAuthFailed &&
- !(mAuthHeader.isDigest() && mAuthHeader.getStale()));
- }
-
- /**
- * @return The last SSL error or null if there is none
- */
- SslError sslError() {
- return mSslError;
- }
-
- /**
- * Handles SSL error(s) on the way down from the user
- * (the user has already provided their feedback).
- */
- void handleSslErrorResponse(boolean proceed) {
- if (mRequestHandle != null) {
- mRequestHandle.handleSslErrorResponse(proceed);
- }
- if (!proceed) {
- mBrowserFrame.stopLoading();
- tearDown();
- }
- }
-
- /**
- * Uses user-supplied credentials to restart a request. If the credentials
- * are null, cancel the request.
- */
- void handleAuthResponse(String username, String password) {
- if (DebugFlags.LOAD_LISTENER) {
- Log.v(LOGTAG, "LoadListener.handleAuthResponse: url: " + mUrl
- + " username: " + username
- + " password: " + password);
- }
- if (username != null && password != null) {
- makeAuthResponse(username, password);
- } else {
- // Commit whatever data we have and tear down the loader.
- commitLoad();
- tearDown();
- }
- }
-
- void makeAuthResponse(String username, String password) {
- if (mAuthHeader == null || mRequestHandle == null) {
- return;
- }
-
- mAuthHeader.setUsername(username);
- mAuthHeader.setPassword(password);
-
- int scheme = mAuthHeader.getScheme();
- if (scheme == HttpAuthHeader.BASIC) {
- // create a basic response
- boolean isProxy = mAuthHeader.isProxy();
-
- mRequestHandle.setupBasicAuthResponse(isProxy, username, password);
- } else if (scheme == HttpAuthHeader.DIGEST) {
- // create a digest response
- boolean isProxy = mAuthHeader.isProxy();
-
- String realm = mAuthHeader.getRealm();
- String nonce = mAuthHeader.getNonce();
- String qop = mAuthHeader.getQop();
- String algorithm = mAuthHeader.getAlgorithm();
- String opaque = mAuthHeader.getOpaque();
-
- mRequestHandle.setupDigestAuthResponse(isProxy, username, password,
- realm, nonce, qop, algorithm, opaque);
- }
- }
-
- /**
- * This is called when a request can be satisfied by the cache, however,
- * the cache result could be a redirect. In this case we need to issue
- * the network request.
- * @param method
- * @param headers
- * @param postData
- */
- void setRequestData(String method, Map<String, String> headers,
- byte[] postData) {
- mMethod = method;
- mRequestHeaders = headers;
- mPostData = postData;
- }
-
- /**
- * @return The current URL associated with this load.
- */
- String url() {
- return mUrl;
- }
-
- /**
- * @return The current WebAddress associated with this load.
- */
- WebAddress getWebAddress() {
- return mUri;
- }
-
- /**
- * @return URL hostname (current URL).
- */
- String host() {
- if (mUri != null) {
- return mUri.getHost();
- }
-
- return null;
- }
-
- /**
- * @return The original URL associated with this load.
- */
- String originalUrl() {
- if (mOriginalUrl != null) {
- return mOriginalUrl;
- } else {
- return mUrl;
- }
- }
-
- long postIdentifier() {
- return mPostIdentifier;
- }
-
- void attachRequestHandle(RequestHandle requestHandle) {
- if (DebugFlags.LOAD_LISTENER) {
- Log.v(LOGTAG, "LoadListener.attachRequestHandle(): " +
- "requestHandle: " + requestHandle);
- }
- mRequestHandle = requestHandle;
- }
-
- void detachRequestHandle() {
- if (DebugFlags.LOAD_LISTENER) {
- Log.v(LOGTAG, "LoadListener.detachRequestHandle(): " +
- "requestHandle: " + mRequestHandle);
- }
- mRequestHandle = null;
- }
-
- /*
- * This function is called from native WebCore code to
- * notify this LoadListener that the content it is currently
- * downloading should be saved to a file and not sent to
- * WebCore.
- */
- void downloadFile() {
- // remove the cache
- WebViewWorker.getHandler().obtainMessage(
- WebViewWorker.MSG_REMOVE_CACHE, this).sendToTarget();
-
- // Inform the client that they should download a file
- mBrowserFrame.getCallbackProxy().onDownloadStart(url(),
- mBrowserFrame.getUserAgentString(),
- mHeaders.getContentDisposition(),
- mMimeType, mContentLength);
-
- // Cancel the download. We need to stop the http load.
- // The native loader object will get cleared by the call to
- // cancel() but will also be cleared on the WebCore side
- // when this function returns.
- cancel();
- }
-
- /*
- * This function is called from native WebCore code to
- * find out if the given URL is in the cache, and if it can
- * be used. This is just for forward/back navigation to a POST
- * URL.
- */
- private static boolean willLoadFromCache(String url, long identifier) {
- assert !JniUtil.useChromiumHttpStack();
- boolean inCache =
- CacheManager.getCacheFile(url, identifier, null) != null;
- if (DebugFlags.LOAD_LISTENER) {
- Log.v(LOGTAG, "willLoadFromCache: " + url + " in cache: " +
- inCache);
- }
- return inCache;
- }
-
- /*
- * Reset the cancel flag. This is used when we are resuming a stopped
- * download. To suspend a download, we cancel it. It can also be cancelled
- * when it has run out of disk space. In this situation, the download
- * can be resumed.
- */
- void resetCancel() {
- mCancelled = false;
- }
-
- String mimeType() {
- return mMimeType;
- }
-
- String transferEncoding() {
- return mTransferEncoding;
- }
-
- /*
- * Return the size of the content being downloaded. This represents the
- * full content size, even under the situation where the download has been
- * resumed after interruption.
- *
- * @ return full content size
- */
- long contentLength() {
- return mContentLength;
- }
-
- // Commit the headers if the status code is not a redirect.
- private void commitHeadersCheckRedirect() {
- if (mCancelled) return;
-
- // do not call webcore if it is redirect. According to the code in
- // InspectorController::willSendRequest(), the response is only updated
- // when it is not redirect. If we received a not-modified response from
- // the server and mCacheLoader is not null, do not send the response to
- // webkit. This is just a validation response for loading from the
- // cache.
- if ((mStatusCode >= 301 && mStatusCode <= 303) || mStatusCode == 307 ||
- (mStatusCode == 304 && mCacheLoader != null)) {
- return;
- }
-
- commitHeaders();
- }
-
- // This commits the headers without checking the response status code.
- private void commitHeaders() {
- if (mIsMainPageLoader && CertTool.getCertType(mMimeType) != null) {
- // In the case of downloading certificate, we will save it to the
- // KeyStore in commitLoad. Do not call webcore.
- return;
- }
-
- // If the response is an authentication and we've resent the
- // request with some credentials then don't commit the headers
- // of this response; wait for the response to the request with the
- // credentials.
- if (mAuthHeader != null) {
- return;
- }
-
- setNativeResponse();
- }
-
- private void setNativeResponse() {
- int nativeResponse = createNativeResponse();
- // The native code deletes the native response object.
- nativeReceivedResponse(nativeResponse);
- mSetNativeResponse = true;
- }
-
- /**
- * Create a WebCore response object so that it can be used by
- * nativeReceivedResponse or nativeRedirectedToUrl
- * @return native response pointer
- */
- private int createNativeResponse() {
- // If WebCore sends if-modified-since, mCacheLoader is null. If
- // CacheManager sends it, mCacheLoader is not null. In this case, if the
- // server responds with a 304, then we treat it like it was a 200 code
- // and proceed with loading the file from the cache.
- int statusCode = (mStatusCode == HTTP_NOT_MODIFIED &&
- mCacheLoader != null) ? HTTP_OK : mStatusCode;
- // pass content-type content-length and content-encoding
- final int nativeResponse = nativeCreateResponse(
- originalUrl(), statusCode, mStatusText,
- mMimeType, mContentLength, mEncoding);
- if (mHeaders != null) {
- mHeaders.getHeaders(new Headers.HeaderCallback() {
- public void header(String name, String value) {
- nativeSetResponseHeader(nativeResponse, name, value);
- }
- });
- }
- return nativeResponse;
- }
-
- /**
- * Commit the load. It should be ok to call repeatedly but only before
- * tearDown is called.
- */
- private void commitLoad() {
- if (mCancelled) return;
- if (!mSetNativeResponse) {
- setNativeResponse();
- }
-
- if (mIsMainPageLoader) {
- String type = CertTool.getCertType(mMimeType);
- if (type != null) {
- // This must be synchronized so that no more data can be added
- // after getByteSize returns.
- synchronized (mDataBuilder) {
- // In the case of downloading certificate, we will save it
- // to the KeyStore and stop the current loading so that it
- // will not generate a new history page
- byte[] cert = new byte[mDataBuilder.getByteSize()];
- int offset = 0;
- while (true) {
- ByteArrayBuilder.Chunk c = mDataBuilder.getFirstChunk();
- if (c == null) break;
-
- if (c.mLength != 0) {
- System.arraycopy(c.mArray, 0, cert, offset, c.mLength);
- offset += c.mLength;
- }
- c.release();
- }
- CertTool.addCertificate(mContext, type, cert);
- mBrowserFrame.stopLoading();
- return;
- }
- }
- }
-
- // Give the data to WebKit now. We don't have to synchronize on
- // mDataBuilder here because pulling each chunk removes it from the
- // internal list so it cannot be modified.
- ByteArrayBuilder.Chunk c;
- while (true) {
- c = mDataBuilder.getFirstChunk();
- if (c == null) break;
-
- if (c.mLength != 0) {
- nativeAddData(c.mArray, c.mLength);
- WebViewWorker.CacheData data = new WebViewWorker.CacheData();
- data.mListener = this;
- data.mChunk = c;
- WebViewWorker.getHandler().obtainMessage(
- WebViewWorker.MSG_APPEND_CACHE, data).sendToTarget();
- } else {
- c.release();
- }
- }
- }
-
- /**
- * Tear down the load. Subclasses should clean up any mess because of
- * cancellation or errors during the load.
- */
- void tearDown() {
- if (getErrorID() == OK) {
- WebViewWorker.CacheSaveData data = new WebViewWorker.CacheSaveData();
- data.mListener = this;
- data.mUrl = mUrl;
- data.mPostId = mPostIdentifier;
- WebViewWorker.getHandler().obtainMessage(
- WebViewWorker.MSG_SAVE_CACHE, data).sendToTarget();
- } else {
- WebViewWorker.getHandler().obtainMessage(
- WebViewWorker.MSG_REMOVE_CACHE, this).sendToTarget();
- }
- if (mNativeLoader != 0) {
- if (!mSetNativeResponse) {
- setNativeResponse();
- }
-
- nativeFinished();
- clearNativeLoader();
- }
- }
-
- /**
- * Helper for getting the error ID.
- * @return errorID.
- */
- private int getErrorID() {
- return mErrorID;
- }
-
- /**
- * Return the error description.
- * @return errorDescription.
- */
- private String getErrorDescription() {
- return mErrorDescription;
- }
-
- /**
- * Notify the loader we encountered an error.
- */
- void notifyError() {
- if (mNativeLoader != 0) {
- String description = getErrorDescription();
- if (description == null) description = "";
- nativeError(getErrorID(), description, url());
- clearNativeLoader();
- }
- }
-
- /**
- * Pause the load. For example, if a plugin is unable to accept more data,
- * we pause reading from the request. Called directly from the WebCore thread.
- */
- void pauseLoad(boolean pause) {
- if (mRequestHandle != null) {
- mRequestHandle.pauseRequest(pause);
- }
- }
-
- /**
- * Cancel a request.
- * FIXME: This will only work if the request has yet to be handled. This
- * is in no way guarenteed if requests are served in a separate thread.
- * It also causes major problems if cancel is called during an
- * EventHandler's method call.
- */
- public void cancel() {
- if (DebugFlags.LOAD_LISTENER) {
- if (mRequestHandle == null) {
- Log.v(LOGTAG, "LoadListener.cancel(): no requestHandle");
- } else {
- Log.v(LOGTAG, "LoadListener.cancel()");
- }
- }
- if (mRequestHandle != null) {
- mRequestHandle.cancel();
- mRequestHandle = null;
- }
-
- WebViewWorker.getHandler().obtainMessage(
- WebViewWorker.MSG_REMOVE_CACHE, this).sendToTarget();
- mCancelled = true;
-
- clearNativeLoader();
- }
-
- // This count is transferred from RequestHandle to LoadListener when
- // loading from the cache so that we can detect redirect loops that switch
- // between the network and the cache.
- private int mCacheRedirectCount;
-
- /*
- * Perform the actual redirection. This involves setting up the new URL,
- * informing WebCore and then telling the Network to start loading again.
- */
- private void doRedirect() {
- // as cancel() can cancel the load before doRedirect() is
- // called through handleMessage, needs to check to see if we
- // are canceled before proceed
- if (mCancelled) {
- return;
- }
-
- // Do the same check for a redirect loop that
- // RequestHandle.setupRedirect does.
- if (mCacheRedirectCount >= RequestHandle.MAX_REDIRECT_COUNT) {
- handleError(EventHandler.ERROR_REDIRECT_LOOP, mContext.getString(
- R.string.httpErrorRedirectLoop));
- return;
- }
-
- String redirectTo = mHeaders.getLocation();
- if (redirectTo != null) {
- int nativeResponse = createNativeResponse();
- redirectTo =
- nativeRedirectedToUrl(mUrl, redirectTo, nativeResponse);
- // nativeRedirectedToUrl() may call cancel(), e.g. when redirect
- // from a https site to a http site, check mCancelled again
- if (mCancelled) {
- return;
- }
- if (redirectTo == null) {
- Log.d(LOGTAG, "Redirection failed for "
- + mHeaders.getLocation());
- cancel();
- return;
- } else if (!URLUtil.isNetworkUrl(redirectTo)) {
- final String text = mContext
- .getString(R.string.open_permission_deny)
- + "\n" + redirectTo;
- if (!mSetNativeResponse) {
- setNativeResponse();
- }
- nativeAddData(text.getBytes(), text.length());
- nativeFinished();
- clearNativeLoader();
- return;
- }
-
-
- // Cache the redirect response
- if (getErrorID() == OK) {
- WebViewWorker.CacheSaveData data = new WebViewWorker.CacheSaveData();
- data.mListener = this;
- data.mUrl = mUrl;
- data.mPostId = mPostIdentifier;
- WebViewWorker.getHandler().obtainMessage(
- WebViewWorker.MSG_SAVE_CACHE, data).sendToTarget();
- } else {
- WebViewWorker.getHandler().obtainMessage(
- WebViewWorker.MSG_REMOVE_CACHE, this).sendToTarget();
- }
-
- // Saving a copy of the unstripped url for the response
- mOriginalUrl = redirectTo;
- // This will strip the anchor
- setUrl(redirectTo);
-
- // Redirect may be in the cache
- if (mRequestHeaders == null) {
- mRequestHeaders = new HashMap<String, String>();
- }
- boolean fromCache = false;
- if (mCacheLoader != null) {
- // This is a redirect from the cache loader. Increment the
- // redirect count to avoid redirect loops.
- mCacheRedirectCount++;
- fromCache = true;
- }
- if (!checkCache(mRequestHeaders)) {
- // mRequestHandle can be null when the request was satisfied
- // by the cache, and the cache returned a redirect
- if (mRequestHandle != null) {
- try {
- mRequestHandle.setupRedirect(mUrl, mStatusCode,
- mRequestHeaders);
- } catch(RuntimeException e) {
- Log.e(LOGTAG, e.getMessage());
- // Signal a bad url error if we could not load the
- // redirection.
- handleError(EventHandler.ERROR_BAD_URL,
- mContext.getString(R.string.httpErrorBadUrl));
- return;
- }
- } else {
- // If the original request came from the cache, there is no
- // RequestHandle, we have to create a new one through
- // Network.requestURL.
- Network network = Network.getInstance(getContext());
- if (!network.requestURL(mMethod, mRequestHeaders,
- mPostData, this)) {
- // Signal a bad url error if we could not load the
- // redirection.
- handleError(EventHandler.ERROR_BAD_URL,
- mContext.getString(R.string.httpErrorBadUrl));
- return;
- }
- }
- if (fromCache) {
- // If we are coming from a cache load, we need to transfer
- // the redirect count to the new (or old) RequestHandle to
- // keep the redirect count in sync.
- mRequestHandle.setRedirectCount(mCacheRedirectCount);
- }
- } else if (!fromCache) {
- // Switching from network to cache means we need to grab the
- // redirect count from the RequestHandle to keep the count in
- // sync. Add 1 to account for the current redirect.
- mCacheRedirectCount = mRequestHandle.getRedirectCount() + 1;
- }
- } else {
- commitHeaders();
- commitLoad();
- tearDown();
- }
-
- if (DebugFlags.LOAD_LISTENER) {
- Log.v(LOGTAG, "LoadListener.onRedirect(): redirect to: " +
- redirectTo);
- }
- }
-
- /**
- * Parses the content-type header.
- * The first part only allows '-' if it follows x or X.
- */
- private static final Pattern CONTENT_TYPE_PATTERN =
- Pattern.compile("^((?:[xX]-)?[a-zA-Z\\*]+/[\\w\\+\\*-]+[\\.[\\w\\+-]+]*)$");
-
- /* package */ void parseContentTypeHeader(String contentType) {
- if (DebugFlags.LOAD_LISTENER) {
- Log.v(LOGTAG, "LoadListener.parseContentTypeHeader: " +
- "contentType: " + contentType);
- }
-
- if (contentType != null) {
- int i = contentType.indexOf(';');
- if (i >= 0) {
- mMimeType = contentType.substring(0, i);
-
- int j = contentType.indexOf('=', i);
- if (j > 0) {
- i = contentType.indexOf(';', j);
- if (i < j) {
- i = contentType.length();
- }
- mEncoding = contentType.substring(j + 1, i);
- } else {
- mEncoding = contentType.substring(i + 1);
- }
- // Trim excess whitespace.
- mEncoding = mEncoding.trim().toLowerCase();
-
- if (i < contentType.length() - 1) {
- // for data: uri the mimeType and encoding have
- // the form image/jpeg;base64 or text/plain;charset=utf-8
- // or text/html;charset=utf-8;base64
- mTransferEncoding =
- contentType.substring(i + 1).trim().toLowerCase();
- }
- } else {
- mMimeType = contentType;
- }
-
- // Trim leading and trailing whitespace
- mMimeType = mMimeType.trim();
-
- try {
- Matcher m = CONTENT_TYPE_PATTERN.matcher(mMimeType);
- if (m.find()) {
- mMimeType = m.group(1);
- } else {
- guessMimeType();
- }
- } catch (IllegalStateException ex) {
- guessMimeType();
- }
- }
- // Ensure mMimeType is lower case.
- mMimeType = mMimeType.toLowerCase();
- }
-
- /**
- * @return The HTTP-authentication object or null if there
- * is no supported scheme in the header.
- * If there are several valid schemes present, we pick the
- * strongest one. If there are several schemes of the same
- * strength, we pick the one that comes first.
- */
- private HttpAuthHeader parseAuthHeader(String header) {
- if (header != null) {
- int posMax = 256;
- int posLen = 0;
- int[] pos = new int [posMax];
-
- int headerLen = header.length();
- if (headerLen > 0) {
- // first, we find all unquoted instances of 'Basic' and 'Digest'
- boolean quoted = false;
- for (int i = 0; i < headerLen && posLen < posMax; ++i) {
- if (header.charAt(i) == '\"') {
- quoted = !quoted;
- } else {
- if (!quoted) {
- if (header.regionMatches(true, i,
- HttpAuthHeader.BASIC_TOKEN, 0,
- HttpAuthHeader.BASIC_TOKEN.length())) {
- pos[posLen++] = i;
- continue;
- }
-
- if (header.regionMatches(true, i,
- HttpAuthHeader.DIGEST_TOKEN, 0,
- HttpAuthHeader.DIGEST_TOKEN.length())) {
- pos[posLen++] = i;
- continue;
- }
- }
- }
- }
- }
-
- if (posLen > 0) {
- // consider all digest schemes first (if any)
- for (int i = 0; i < posLen; i++) {
- if (header.regionMatches(true, pos[i],
- HttpAuthHeader.DIGEST_TOKEN, 0,
- HttpAuthHeader.DIGEST_TOKEN.length())) {
- String sub = header.substring(pos[i],
- (i + 1 < posLen ? pos[i + 1] : headerLen));
-
- HttpAuthHeader rval = new HttpAuthHeader(sub);
- if (rval.isSupportedScheme()) {
- // take the first match
- return rval;
- }
- }
- }
-
- // ...then consider all basic schemes (if any)
- for (int i = 0; i < posLen; i++) {
- if (header.regionMatches(true, pos[i],
- HttpAuthHeader.BASIC_TOKEN, 0,
- HttpAuthHeader.BASIC_TOKEN.length())) {
- String sub = header.substring(pos[i],
- (i + 1 < posLen ? pos[i + 1] : headerLen));
-
- HttpAuthHeader rval = new HttpAuthHeader(sub);
- if (rval.isSupportedScheme()) {
- // take the first match
- return rval;
- }
- }
- }
- }
- }
-
- return null;
- }
-
- /**
- * If the content is a redirect or not modified we should not send
- * any data into WebCore as that will cause it create a document with
- * the data, then when we try to provide the real content, it will assert.
- *
- * @return True iff the callback should be ignored.
- */
- private boolean ignoreCallbacks() {
- return (mCancelled || mAuthHeader != null ||
- // Allow 305 (Use Proxy) to call through.
- (mStatusCode > 300 && mStatusCode < 400 && mStatusCode != 305));
- }
-
- /**
- * Sets the current URL associated with this load.
- */
- void setUrl(String url) {
- if (url != null) {
- mUri = null;
- if (URLUtil.isNetworkUrl(url)) {
- mUrl = URLUtil.stripAnchor(url);
- try {
- mUri = new WebAddress(mUrl);
- } catch (ParseException e) {
- e.printStackTrace();
- }
- } else {
- mUrl = url;
- }
- }
- }
-
- /**
- * Guesses MIME type if one was not specified. Defaults to 'text/html'. In
- * addition, tries to guess the MIME type based on the extension.
- *
- */
- private void guessMimeType() {
- // Data urls must have a valid mime type or a blank string for the mime
- // type (implying text/plain).
- if (URLUtil.isDataUrl(mUrl) && mMimeType.length() != 0) {
- cancel();
- final String text = mContext.getString(R.string.httpErrorBadUrl);
- handleError(EventHandler.ERROR_BAD_URL, text);
- } else {
- // Note: This is ok because this is used only for the main content
- // of frames. If no content-type was specified, it is fine to
- // default to text/html.
- mMimeType = "text/html";
- String newMimeType = guessMimeTypeFromExtension(mUrl);
- if (newMimeType != null) {
- mMimeType = newMimeType;
- }
- }
- }
-
- /**
- * guess MIME type based on the file extension.
- */
- private String guessMimeTypeFromExtension(String url) {
- // PENDING: need to normalize url
- if (DebugFlags.LOAD_LISTENER) {
- Log.v(LOGTAG, "guessMimeTypeFromExtension: url = " + url);
- }
-
- return MimeTypeMap.getSingleton().getMimeTypeFromExtension(
- MimeTypeMap.getFileExtensionFromUrl(url));
- }
-
- /**
- * Either send a message to ourselves or queue the message if this is a
- * synchronous load.
- */
- private void sendMessageInternal(Message msg) {
- if (mSynchronous) {
- mMessageQueue.add(msg);
- } else {
- sendMessage(msg);
- }
- }
-
- /**
- * Cycle through our messages for synchronous loads.
- */
- /* package */ void loadSynchronousMessages() {
- if (DebugFlags.LOAD_LISTENER && !mSynchronous) {
- throw new AssertionError();
- }
- // Note: this can be called twice if it is a synchronous network load,
- // and there is a cache, but it needs to go to network to validate. If
- // validation succeed, the CacheLoader is used so this is first called
- // from http thread. Then it is called again from WebViewCore thread
- // after the load is completed. So make sure the queue is cleared but
- // don't set it to null.
- while (!mMessageQueue.isEmpty()) {
- handleMessage(mMessageQueue.remove(0));
- }
- }
-
- //=========================================================================
- // native functions
- //=========================================================================
-
- /**
- * Create a new native response object.
- * @param url The url of the resource.
- * @param statusCode The HTTP status code.
- * @param statusText The HTTP status text.
- * @param mimeType HTTP content-type.
- * @param expectedLength An estimate of the content length or the length
- * given by the server.
- * @param encoding HTTP encoding.
- * @return The native response pointer.
- */
- private native int nativeCreateResponse(String url, int statusCode,
- String statusText, String mimeType, long expectedLength,
- String encoding);
-
- /**
- * Add a response header to the native object.
- * @param nativeResponse The native pointer.
- * @param key String key.
- * @param val String value.
- */
- private native void nativeSetResponseHeader(int nativeResponse, String key,
- String val);
-
- /**
- * Dispatch the response.
- * @param nativeResponse The native pointer.
- */
- private native void nativeReceivedResponse(int nativeResponse);
-
- /**
- * Add data to the loader.
- * @param data Byte array of data.
- * @param length Number of objects in data.
- */
- private native void nativeAddData(byte[] data, int length);
-
- /**
- * Tell the loader it has finished.
- */
- private native void nativeFinished();
-
- /**
- * tell the loader to redirect
- * @param baseUrl The base url.
- * @param redirectTo The url to redirect to.
- * @param nativeResponse The native pointer.
- * @return The new url that the resource redirected to.
- */
- private native String nativeRedirectedToUrl(String baseUrl,
- String redirectTo, int nativeResponse);
-
- /**
- * Tell the loader there is error
- * @param id
- * @param desc
- * @param failingUrl The url that failed.
- */
- private native void nativeError(int id, String desc, String failingUrl);
-
-}
diff --git a/core/java/android/webkit/Network.java b/core/java/android/webkit/Network.java
deleted file mode 100644
index ee9b949..0000000
--- a/core/java/android/webkit/Network.java
+++ /dev/null
@@ -1,394 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.webkit;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.net.http.*;
-import android.os.*;
-import android.util.Log;
-
-import java.io.ByteArrayInputStream;
-import java.io.InputStream;
-import java.util.Map;
-
-import junit.framework.Assert;
-
-class Network {
-
- private static final String LOGTAG = "network";
-
- /**
- * Static instance of a Network object.
- */
- private static Network sNetwork;
-
- /**
- * Flag to store the state of platform notifications, for the case
- * when the Network object has not been constructed yet
- */
- private static boolean sPlatformNotifications;
-
- /**
- * Reference count for platform notifications as the network class is a
- * static and can exist over multiple activities, thus over multiple
- * onPause/onResume pairs.
- */
- private static int sPlatformNotificationEnableRefCount;
-
- /**
- * Proxy username if known (used for pre-emptive proxy authentication).
- */
- private String mProxyUsername;
-
- /**
- * Proxy password if known (used for pre-emptive proxy authentication).
- */
- private String mProxyPassword;
-
- /**
- * Network request queue (requests are added from the browser thread).
- */
- private RequestQueue mRequestQueue;
-
- /**
- * SSL error handler: takes care of synchronization of multiple async
- * loaders with SSL-related problems.
- */
- private SslErrorHandlerImpl mSslErrorHandler;
-
- /**
- * HTTP authentication handler: takes care of synchronization of HTTP
- * authentication requests.
- */
- private HttpAuthHandlerImpl mHttpAuthHandler;
-
- private Context mContext;
-
- /**
- * True if the currently used network connection is a roaming phone
- * connection.
- */
- private boolean mRoaming;
-
- /**
- * Tracks if we are roaming.
- */
- private RoamingMonitor mRoamingMonitor;
-
- /**
- * @return The singleton instance of the network.
- */
- public static synchronized Network getInstance(Context context) {
- if (sNetwork == null) {
- // Note Context of the Application is used here, rather than
- // the what is passed in (usually a Context derived from an
- // Activity) so the intent receivers belong to the application
- // rather than an activity - this fixes the issue where
- // Activities are created and destroyed during the lifetime of
- // an Application
- sNetwork = new Network(context.getApplicationContext());
- if (sPlatformNotifications) {
- // Adjust the ref count before calling enable as it is already
- // taken into account when the static function was called
- // directly
- --sPlatformNotificationEnableRefCount;
- enablePlatformNotifications();
- }
- }
- return sNetwork;
- }
-
-
- /**
- * Enables data state and proxy tracking
- */
- public static void enablePlatformNotifications() {
- if (++sPlatformNotificationEnableRefCount == 1) {
- if (sNetwork != null) {
- sNetwork.mRequestQueue.enablePlatformNotifications();
- sNetwork.monitorRoaming();
- } else {
- sPlatformNotifications = true;
- }
- }
- }
-
- /**
- * If platform notifications are enabled, this should be called
- * from onPause() or onStop()
- */
- public static void disablePlatformNotifications() {
- if (--sPlatformNotificationEnableRefCount == 0) {
- if (sNetwork != null) {
- sNetwork.mRequestQueue.disablePlatformNotifications();
- sNetwork.stopMonitoringRoaming();
- } else {
- sPlatformNotifications = false;
- }
- }
- }
-
- /**
- * Creates a new Network object.
- * XXX: Must be created in the same thread as WebCore!!!!!
- */
- private Network(Context context) {
- if (DebugFlags.NETWORK) {
- Assert.assertTrue(Thread.currentThread().
- getName().equals(WebViewCore.THREAD_NAME));
- }
- mContext = context;
- mSslErrorHandler = new SslErrorHandlerImpl();
- mHttpAuthHandler = new HttpAuthHandlerImpl(this);
-
- mRequestQueue = new RequestQueue(context);
- }
-
- private class RoamingMonitor extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (!ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction()))
- return;
-
- final ConnectivityManager connManager = (ConnectivityManager) context
- .getSystemService(Context.CONNECTIVITY_SERVICE);
- final NetworkInfo info = connManager.getActiveNetworkInfo();
- if (info != null)
- mRoaming = info.isRoaming();
- };
- };
-
- private void monitorRoaming() {
- mRoamingMonitor = new RoamingMonitor();
- IntentFilter filter = new IntentFilter();
- filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
- mContext.registerReceiver(sNetwork.mRoamingMonitor, filter);
- }
-
- private void stopMonitoringRoaming() {
- if (mRoamingMonitor != null) {
- mContext.unregisterReceiver(mRoamingMonitor);
- mRoamingMonitor = null;
- }
- }
-
- /**
- * Request a url from either the network or the file system.
- * @param url The url to load.
- * @param method The http method.
- * @param headers The http headers.
- * @param postData The body of the request.
- * @param loader A LoadListener for receiving the results of the request.
- * @return True if the request was successfully queued.
- */
- public boolean requestURL(String method,
- Map<String, String> headers,
- byte [] postData,
- LoadListener loader) {
-
- String url = loader.url();
-
- // Not a valid url, return false because we won't service the request!
- if (!URLUtil.isValidUrl(url)) {
- return false;
- }
-
- // asset, res, file system or data stream are handled in the other code
- // path. This only handles network request.
- if (URLUtil.isAssetUrl(url) || URLUtil.isResourceUrl(url)
- || URLUtil.isFileUrl(url) || URLUtil.isDataUrl(url)) {
- return false;
- }
-
- // If this is a prefetch, abort it if we're roaming.
- if (mRoaming && headers.containsKey("X-Moz") && "prefetch".equals(headers.get("X-Moz"))) {
- return false;
- }
-
- /* FIXME: this is lame. Pass an InputStream in, rather than
- making this lame one here */
- InputStream bodyProvider = null;
- int bodyLength = 0;
- if (postData != null) {
- bodyLength = postData.length;
- bodyProvider = new ByteArrayInputStream(postData);
- }
-
- RequestQueue q = mRequestQueue;
- RequestHandle handle = null;
- if (loader.isSynchronous()) {
- handle = q.queueSynchronousRequest(url, loader.getWebAddress(),
- method, headers, loader, bodyProvider, bodyLength);
- loader.attachRequestHandle(handle);
- handle.processRequest();
- loader.loadSynchronousMessages();
- } else {
- handle = q.queueRequest(url, loader.getWebAddress(), method,
- headers, loader, bodyProvider, bodyLength);
- // FIXME: Although this is probably a rare condition, normal network
- // requests are processed in a separate thread. This means that it
- // is possible to process part of the request before setting the
- // request handle on the loader. We should probably refactor this to
- // ensure the handle is attached before processing begins.
- loader.attachRequestHandle(handle);
- }
-
- return true;
- }
-
- /**
- * @return True iff there is a valid proxy set.
- */
- public boolean isValidProxySet() {
- // The proxy host and port can be set within a different thread during
- // an Intent broadcast.
- synchronized (mRequestQueue) {
- return mRequestQueue.getProxyHost() != null;
- }
- }
-
- /**
- * Get the proxy hostname.
- * @return The proxy hostname obtained from the network queue and proxy
- * settings.
- */
- public String getProxyHostname() {
- return mRequestQueue.getProxyHost().getHostName();
- }
-
- /**
- * @return The proxy username or null if none.
- */
- public synchronized String getProxyUsername() {
- return mProxyUsername;
- }
-
- /**
- * Sets the proxy username.
- * @param proxyUsername Username to use when
- * connecting through the proxy.
- */
- public synchronized void setProxyUsername(String proxyUsername) {
- if (DebugFlags.NETWORK) {
- Assert.assertTrue(isValidProxySet());
- }
-
- mProxyUsername = proxyUsername;
- }
-
- /**
- * @return The proxy password or null if none.
- */
- public synchronized String getProxyPassword() {
- return mProxyPassword;
- }
-
- /**
- * Sets the proxy password.
- * @param proxyPassword Password to use when
- * connecting through the proxy.
- */
- public synchronized void setProxyPassword(String proxyPassword) {
- if (DebugFlags.NETWORK) {
- Assert.assertTrue(isValidProxySet());
- }
-
- mProxyPassword = proxyPassword;
- }
-
- /**
- * Saves the state of network handlers (user SSL and HTTP-authentication
- * preferences).
- * @param outState The out-state to save (write) to.
- * @return True iff succeeds.
- */
- public boolean saveState(Bundle outState) {
- if (DebugFlags.NETWORK) {
- Log.v(LOGTAG, "Network.saveState()");
- }
-
- return mSslErrorHandler.saveState(outState);
- }
-
- /**
- * Restores the state of network handlers (user SSL and HTTP-authentication
- * preferences).
- * @param inState The in-state to load (read) from.
- * @return True iff succeeds.
- */
- public boolean restoreState(Bundle inState) {
- if (DebugFlags.NETWORK) {
- Log.v(LOGTAG, "Network.restoreState()");
- }
-
- return mSslErrorHandler.restoreState(inState);
- }
-
- /**
- * Clears user SSL-error preference table.
- */
- public void clearUserSslPrefTable() {
- mSslErrorHandler.clear();
- }
-
- /**
- * Handles SSL error(s) on the way up to the user: the user must decide
- * whether errors should be ignored or not.
- * @param loader The loader that resulted in SSL errors.
- */
- public void handleSslErrorRequest(LoadListener loader) {
- if (DebugFlags.NETWORK) Assert.assertNotNull(loader);
- if (loader != null) {
- mSslErrorHandler.handleSslErrorRequest(loader);
- }
- }
-
- /* package */ boolean checkSslPrefTable(LoadListener loader,
- SslError error) {
- if (loader != null && error != null) {
- return mSslErrorHandler.checkSslPrefTable(loader, error);
- }
- return false;
- }
-
- /**
- * Handles authentication requests on their way up to the user (the user
- * must provide credentials).
- * @param loader The loader that resulted in an HTTP
- * authentication request.
- */
- public void handleAuthRequest(LoadListener loader) {
- if (DebugFlags.NETWORK) Assert.assertNotNull(loader);
- if (loader != null) {
- mHttpAuthHandler.handleAuthRequest(loader);
- }
- }
-
- // Performance probe
- public void startTiming() {
- mRequestQueue.startTiming();
- }
-
- public void stopTiming() {
- mRequestQueue.stopTiming();
- }
-}
diff --git a/core/java/android/webkit/SslErrorHandlerImpl.java b/core/java/android/webkit/SslErrorHandlerImpl.java
deleted file mode 100644
index b2e4b13..0000000
--- a/core/java/android/webkit/SslErrorHandlerImpl.java
+++ /dev/null
@@ -1,273 +0,0 @@
-/*
- * 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 android.webkit;
-
-import android.net.http.SslError;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.util.Log;
-
-import java.util.LinkedList;
-import java.util.ListIterator;
-
-/**
- * SslErrorHandler's implementation for Android Java HTTP stack.
- * This class is not needed if the Chromium HTTP stack is used.
- */
-class SslErrorHandlerImpl extends SslErrorHandler {
- /* One problem here is that there may potentially be multiple SSL errors
- * coming from multiple loaders. Therefore, we keep a queue of loaders
- * that have SSL-related problems and process errors one by one in the
- * order they were received.
- */
-
- private static final String LOGTAG = "network";
-
- /**
- * Queue of loaders that experience SSL-related problems.
- */
- private LinkedList<LoadListener> mLoaderQueue;
-
- /**
- * SSL error preference table.
- */
- private Bundle mSslPrefTable;
-
- // These are only used in the client facing SslErrorHandler.
- private final SslErrorHandler mOriginHandler;
- private final LoadListener mLoadListener;
-
- // Message id for handling the response from the client.
- private static final int HANDLE_RESPONSE = 100;
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case HANDLE_RESPONSE:
- LoadListener loader = (LoadListener) msg.obj;
- synchronized (SslErrorHandlerImpl.this) {
- handleSslErrorResponse(loader, loader.sslError(),
- msg.arg1 == 1);
- mLoaderQueue.remove(loader);
- fastProcessQueuedSslErrors();
- }
- break;
- }
- }
-
- /**
- * Creates a new error handler with an empty loader queue.
- */
- /* package */ SslErrorHandlerImpl() {
- mLoaderQueue = new LinkedList<LoadListener>();
- mSslPrefTable = new Bundle();
-
- // These are used by client facing SslErrorHandlers.
- mOriginHandler = null;
- mLoadListener = null;
- }
-
- /**
- * Create a new error handler that will be passed to the client.
- */
- private SslErrorHandlerImpl(SslErrorHandler origin, LoadListener listener) {
- mOriginHandler = origin;
- mLoadListener = listener;
- }
-
- /**
- * Saves this handler's state into a map.
- * @return True iff succeeds.
- */
- /* package */ synchronized boolean saveState(Bundle outState) {
- boolean success = (outState != null);
- if (success) {
- // TODO?
- outState.putBundle("ssl-error-handler", mSslPrefTable);
- }
-
- return success;
- }
-
- /**
- * Restores this handler's state from a map.
- * @return True iff succeeds.
- */
- /* package */ synchronized boolean restoreState(Bundle inState) {
- boolean success = (inState != null);
- if (success) {
- success = inState.containsKey("ssl-error-handler");
- if (success) {
- mSslPrefTable = inState.getBundle("ssl-error-handler");
- }
- }
-
- return success;
- }
-
- /**
- * Clears SSL error preference table.
- */
- /* package */ synchronized void clear() {
- mSslPrefTable.clear();
- }
-
- /**
- * Handles requests from the network stack about whether to proceed with a
- * load given an SSL error(s). We may ask the client what to do, or use a
- * cached response.
- */
- /* package */ synchronized void handleSslErrorRequest(LoadListener loader) {
- if (DebugFlags.SSL_ERROR_HANDLER) {
- Log.v(LOGTAG, "SslErrorHandler.handleSslErrorRequest(): " +
- "url=" + loader.url());
- }
-
- if (!loader.cancelled()) {
- mLoaderQueue.offer(loader);
- if (loader == mLoaderQueue.peek()) {
- fastProcessQueuedSslErrors();
- }
- }
- }
-
- /**
- * Check the preference table to see if we already have a 'proceed' decision
- * from the client for this host and for an error of equal or greater
- * severity than the supplied error. If so, instruct the loader to proceed
- * and return true. Otherwise return false.
- */
- /* package */ synchronized boolean checkSslPrefTable(LoadListener loader,
- SslError error) {
- final String host = loader.host();
- final int primary = error.getPrimaryError();
-
- if (DebugFlags.SSL_ERROR_HANDLER) {
- assert host != null;
- assert primary != -1;
- }
-
- if (mSslPrefTable.containsKey(host) && primary <= mSslPrefTable.getInt(host)) {
- if (!loader.cancelled()) {
- loader.handleSslErrorResponse(true);
- }
- return true;
- }
- return false;
- }
-
- /**
- * Processes queued SSL-error confirmation requests in
- * a tight loop while there is no need to ask the client.
- */
- /* package */void fastProcessQueuedSslErrors() {
- while (processNextLoader());
- }
-
- /**
- * Processes the next loader in the queue.
- * @return True iff should proceed to processing the
- * following loader in the queue
- */
- private synchronized boolean processNextLoader() {
- LoadListener loader = mLoaderQueue.peek();
- if (loader != null) {
- // if this loader has been cancelled
- if (loader.cancelled()) {
- // go to the following loader in the queue. Make sure this
- // loader has been removed from the queue.
- mLoaderQueue.remove(loader);
- return true;
- }
-
- SslError error = loader.sslError();
-
- if (DebugFlags.SSL_ERROR_HANDLER) {
- assert error != null;
- }
-
- // checkSslPrefTable() will instruct the loader to proceed if we
- // have a cached 'proceed' decision. It does not remove the loader
- // from the queue.
- if (checkSslPrefTable(loader, error)) {
- mLoaderQueue.remove(loader);
- return true;
- }
-
- // If we can not proceed based on a cached decision, ask the client.
- CallbackProxy proxy = loader.getFrame().getCallbackProxy();
- proxy.onReceivedSslError(new SslErrorHandlerImpl(this, loader), error);
- }
-
- // the queue must be empty, stop
- return false;
- }
-
- /**
- * Proceed with this load.
- */
- public void proceed() {
- mOriginHandler.sendMessage(mOriginHandler.obtainMessage(
- HANDLE_RESPONSE, 1, 0, mLoadListener));
- }
-
- /**
- * Cancel this load and all pending loads for the WebView that had the
- * error.
- */
- public void cancel() {
- mOriginHandler.sendMessage(mOriginHandler.obtainMessage(
- HANDLE_RESPONSE, 0, 0, mLoadListener));
- }
-
- /**
- * Handles the response from the client about whether to proceed with this
- * load. We save the response to be re-used in the future.
- */
- /* package */ synchronized void handleSslErrorResponse(LoadListener loader,
- SslError error, boolean proceed) {
- if (DebugFlags.SSL_ERROR_HANDLER) {
- assert loader != null;
- assert error != null;
- }
-
- if (DebugFlags.SSL_ERROR_HANDLER) {
- Log.v(LOGTAG, "SslErrorHandler.handleSslErrorResponse():"
- + " proceed: " + proceed
- + " url:" + loader.url());
- }
-
- if (!loader.cancelled()) {
- if (proceed) {
- // Update the SSL error preference table
- int primary = error.getPrimaryError();
- String host = loader.host();
-
- if (DebugFlags.SSL_ERROR_HANDLER) {
- assert host != null;
- assert primary != -1;
- }
- boolean hasKey = mSslPrefTable.containsKey(host);
- if (!hasKey || primary > mSslPrefTable.getInt(host)) {
- mSslPrefTable.putInt(host, primary);
- }
- }
- loader.handleSslErrorResponse(proceed);
- }
- }
-}
diff --git a/core/java/android/webkit/StreamLoader.java b/core/java/android/webkit/StreamLoader.java
deleted file mode 100644
index 7bcd50d..0000000
--- a/core/java/android/webkit/StreamLoader.java
+++ /dev/null
@@ -1,204 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.webkit;
-
-import android.content.Context;
-import android.net.http.EventHandler;
-import android.net.http.Headers;
-import android.os.Handler;
-import android.os.Message;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * This abstract class is used for all content loaders that rely on streaming
- * content into the rendering engine loading framework.
- *
- * The class implements a state machine to load the content into the frame in
- * a similar manor to the way content arrives from the network. The class uses
- * messages to move from one state to the next, which enables async. loading of
- * the streamed content.
- *
- * Classes that inherit from this class must implement two methods, the first
- * method is used to setup the InputStream and notify the loading framework if
- * it can load it's content. The other method allows the derived class to add
- * additional HTTP headers to the response.
- *
- * By default, content loaded with a StreamLoader is marked with a HTTP header
- * that indicates the content should not be cached.
- *
- */
-abstract class StreamLoader implements Handler.Callback {
-
- private static final int MSG_STATUS = 100; // Send status to loader
- private static final int MSG_HEADERS = 101; // Send headers to loader
- private static final int MSG_DATA = 102; // Send data to loader
- private static final int MSG_END = 103; // Send endData to loader
-
- protected final Context mContext;
- protected final LoadListener mLoadListener; // loader class
- protected InputStream mDataStream; // stream to read data from
- protected long mContentLength; // content length of data
- private byte [] mData; // buffer to pass data to loader with.
-
- // Handler which will be initialized in the thread where load() is called.
- private Handler mHandler;
-
- /**
- * Constructor. Although this class calls the LoadListener, it only calls
- * the EventHandler Interface methods. LoadListener concrete class is used
- * to avoid the penality of calling an interface.
- *
- * @param loadlistener The LoadListener to call with the data.
- */
- StreamLoader(LoadListener loadlistener) {
- mLoadListener = loadlistener;
- mContext = loadlistener.getContext();
- }
-
- /**
- * This method is called when the derived class should setup mDataStream,
- * and call mLoadListener.status() to indicate that the load can occur. If it
- * fails to setup, it should still call status() with the error code.
- *
- * @return true if stream was successfully setup
- */
- protected abstract boolean setupStreamAndSendStatus();
-
- /**
- * This method is called when the headers are about to be sent to the
- * load framework. The derived class has the opportunity to add addition
- * headers.
- *
- * @param headers Map of HTTP headers that will be sent to the loader.
- */
- abstract protected void buildHeaders(Headers headers);
-
- /**
- * Calling this method starts the load of the content for this StreamLoader.
- * This method simply creates a Handler in the current thread and posts a
- * message to send the status and returns immediately.
- */
- final void load() {
- synchronized (this) {
- if (mHandler == null) {
- mHandler = new Handler(this);
- }
- }
-
- if (!mLoadListener.isSynchronous()) {
- mHandler.sendEmptyMessage(MSG_STATUS);
- } else {
- // Load the stream synchronously.
- if (setupStreamAndSendStatus()) {
- // We were able to open the stream, create the array
- // to pass data to the loader
- mData = new byte[8192];
- sendHeaders();
- while (!sendData() && !mLoadListener.cancelled());
- closeStreamAndSendEndData();
- mLoadListener.loadSynchronousMessages();
- }
- }
- }
-
- public boolean handleMessage(Message msg) {
- if (mLoadListener.isSynchronous()) {
- throw new AssertionError();
- }
- if (mLoadListener.cancelled()) {
- closeStreamAndSendEndData();
- return true;
- }
- switch(msg.what) {
- case MSG_STATUS:
- if (setupStreamAndSendStatus()) {
- // We were able to open the stream, create the array
- // to pass data to the loader
- mData = new byte[8192];
- mHandler.sendEmptyMessage(MSG_HEADERS);
- }
- break;
- case MSG_HEADERS:
- sendHeaders();
- mHandler.sendEmptyMessage(MSG_DATA);
- break;
- case MSG_DATA:
- if (sendData()) {
- mHandler.sendEmptyMessage(MSG_END);
- } else {
- mHandler.sendEmptyMessage(MSG_DATA);
- }
- break;
- case MSG_END:
- closeStreamAndSendEndData();
- break;
- default:
- return false;
- }
- return true;
- }
-
- /**
- * Construct the headers and pass them to the EventHandler.
- */
- private void sendHeaders() {
- Headers headers = new Headers();
- if (mContentLength > 0) {
- headers.setContentLength(mContentLength);
- }
- buildHeaders(headers);
- mLoadListener.headers(headers);
- }
-
- /**
- * Read data from the stream and pass it to the EventHandler.
- * If an error occurs reading the stream, then an error is sent to the
- * EventHandler, and moves onto the next state - end of data.
- * @return True if all the data has been read. False if sendData should be
- * called again.
- */
- private boolean sendData() {
- if (mDataStream != null) {
- try {
- int amount = mDataStream.read(mData);
- if (amount > 0) {
- mLoadListener.data(mData, amount);
- return false;
- }
- } catch (IOException ex) {
- mLoadListener.error(EventHandler.FILE_ERROR, ex.getMessage());
- }
- }
- return true;
- }
-
- /**
- * Close the stream and inform the EventHandler that load is complete.
- */
- private void closeStreamAndSendEndData() {
- if (mDataStream != null) {
- try {
- mDataStream.close();
- } catch (IOException ex) {
- // ignore.
- }
- }
- mLoadListener.endData();
- }
-}
diff --git a/core/java/android/webkit/WebResourceResponse.java b/core/java/android/webkit/WebResourceResponse.java
index 24e0d11..650310e 100644
--- a/core/java/android/webkit/WebResourceResponse.java
+++ b/core/java/android/webkit/WebResourceResponse.java
@@ -26,24 +26,6 @@
* response when the WebView requests a particular resource.
*/
public class WebResourceResponse {
-
- private class Loader extends StreamLoader {
- Loader(LoadListener loadListener) {
- super(loadListener);
- mDataStream = mInputStream;
- }
- @Override
- protected boolean setupStreamAndSendStatus() {
- mLoadListener.status(1, 1, mDataStream != null ? 200 : 404, "");
- return true;
- }
- @Override
- protected void buildHeaders(Headers headers) {
- headers.setContentType(mMimeType);
- headers.setContentEncoding(mEncoding);
- }
- }
-
// Accessed by jni, do not rename without modifying the jni code.
private String mMimeType;
private String mEncoding;
@@ -114,8 +96,4 @@
public InputStream getData() {
return mInputStream;
}
-
- StreamLoader loader(LoadListener listener) {
- return new Loader(listener);
- }
}
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..a399c32 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;
@@ -119,6 +122,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.HashMap;
@@ -369,6 +373,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);
@@ -376,21 +383,24 @@
@Override
public boolean sendKeyEvent(KeyEvent event) {
- // Latin IME occasionally sends delete codes directly using
- // sendKeyEvents. WebViewInputConnection should treat this
- // as a deleteSurroundingText.
- if (!mIsKeySentByMe
- && event.getKeyCode() == KeyEvent.KEYCODE_DEL) {
- Editable editable = getEditable();
- int selectionStart = Selection.getSelectionStart(editable);
- int selectionEnd = Selection.getSelectionEnd(editable);
- if (selectionEnd > 0 && (selectionStart == selectionEnd)) {
- int action = event.getAction();
- if (action == KeyEvent.ACTION_UP) {
+ // Some IMEs send key events directly using sendKeyEvents.
+ // WebViewInputConnection should treat these as text changes.
+ if (!mIsKeySentByMe) {
+ if (event.getAction() == KeyEvent.ACTION_UP) {
+ if (event.getKeyCode() == KeyEvent.KEYCODE_DEL) {
return deleteSurroundingText(1, 0);
- } else if (action == KeyEvent.ACTION_DOWN) {
- return true; // the delete will happen in ACTION_UP
+ } else if (event.getKeyCode() == KeyEvent.KEYCODE_FORWARD_DEL) {
+ return deleteSurroundingText(0, 1);
+ } else if (event.getUnicodeChar() != 0){
+ String newComposingText =
+ Character.toString((char)event.getUnicodeChar());
+ return commitText(newComposingText, 1);
}
+ } else if (event.getAction() == KeyEvent.ACTION_DOWN &&
+ (event.getKeyCode() == KeyEvent.KEYCODE_DEL
+ || event.getKeyCode() == KeyEvent.KEYCODE_FORWARD_DEL
+ || event.getUnicodeChar() != 0)) {
+ return true; // only act on action_down
}
}
return super.sendKeyEvent(event);
@@ -450,6 +460,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
@@ -475,13 +556,17 @@
&& TextUtils.regionMatches(text, 0, original, 0,
textLength);
}
+ boolean sendChange = false;
if (isCharacterAdd) {
- sendCharacter(text.charAt(textLength - 1));
+ sendChange = !sendCharacter(text.charAt(textLength - 1));
} else if (isCharacterDelete) {
sendDeleteKey();
- } else if (textLength != originalLength ||
- !TextUtils.regionMatches(text, 0, original, 0,
- textLength)) {
+ } else {
+ sendChange = (textLength != originalLength) ||
+ !TextUtils.regionMatches(text, 0, original, 0,
+ textLength);
+ }
+ if (sendChange) {
// Send a message so that key strokes and text replacement
// do not come out of order.
Message replaceMessage = mPrivateHandler.obtainMessage(
@@ -495,18 +580,20 @@
* Send a single character to the WebView as a key down and up event.
* @param c The character to be sent.
*/
- private void sendCharacter(char c) {
+ private boolean sendCharacter(char c) {
if (mKeyCharacterMap == null) {
mKeyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
}
char[] chars = new char[1];
chars[0] = c;
KeyEvent[] events = mKeyCharacterMap.getEvents(chars);
- if (events != null) {
+ boolean mapsToKeyEvent = (events != null);
+ if (mapsToKeyEvent) {
for (KeyEvent event : events) {
sendKeyEvent(event);
}
}
+ return mapsToKeyEvent;
}
/**
@@ -831,14 +918,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 +1030,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 +1437,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();
@@ -1953,7 +2049,6 @@
public static void enablePlatformNotifications() {
checkThread();
synchronized (WebView.class) {
- Network.enablePlatformNotifications();
sNotificationsEnabled = true;
Context context = JniUtil.getContext();
if (context != null)
@@ -1971,7 +2066,6 @@
public static void disablePlatformNotifications() {
checkThread();
synchronized (WebView.class) {
- Network.disablePlatformNotifications();
sNotificationsEnabled = false;
Context context = JniUtil.getContext();
if (context != null)
@@ -4621,15 +4715,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 +4728,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 +5125,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 +5265,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 +5585,9 @@
+ "keyCode=" + keyCode
+ ", " + event + ", unicode=" + event.getUnicodeChar());
}
+ if (mIsCaretSelection) {
+ selectionDone();
+ }
if (mBlockWebkitViewMessages) {
return false;
}
@@ -5810,6 +5900,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 +5921,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 +5936,9 @@
private void updateWebkitSelection() {
int[] handles = null;
+ if (mIsCaretSelection) {
+ mSelectCursorExtent.set(mSelectCursorBase);
+ }
if (mSelectingText) {
handles = new int[4];
handles[0] = mSelectCursorBase.centerX();
@@ -5854,6 +5952,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 +5987,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 +6608,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 +7278,9 @@
if (mSelectingText) {
mSelectionStarted = false;
+ if (mIsCaretSelection) {
+ resetCaretTimer();
+ }
syncSelectionCursors();
invalidate();
}
@@ -7801,7 +7923,10 @@
}
}, ViewConfiguration.getPressedStateDuration());
}
- if (sDisableNavcache) {
+ if (mFocusedNode != null && mFocusedNode.mIntentUrl != null) {
+ playSoundEffect(SoundEffectConstants.CLICK);
+ overrideLoading(mFocusedNode.mIntentUrl);
+ } else if (sDisableNavcache) {
WebViewCore.TouchUpData touchUpData = new WebViewCore.TouchUpData();
// use "0" as generation id to inform WebKit to use the same x/y as
// it used when processing GET_TOUCH_HIGHLIGHT_RECTS
@@ -9039,24 +9164,7 @@
WebKitHitTest hit = (WebKitHitTest) msg.obj;
mFocusedNode = hit;
setTouchHighlightRects(hit);
- if (hit == null) {
- mInitialHitTestResult = null;
- } else {
- mInitialHitTestResult = new HitTestResult();
- if (hit.mLinkUrl != null) {
- mInitialHitTestResult.mType = HitTestResult.SRC_ANCHOR_TYPE;
- mInitialHitTestResult.mExtra = hit.mLinkUrl;
- if (hit.mImageUrl != null) {
- mInitialHitTestResult.mType = HitTestResult.SRC_IMAGE_ANCHOR_TYPE;
- mInitialHitTestResult.mExtra = hit.mImageUrl;
- }
- } else if (hit.mImageUrl != null) {
- mInitialHitTestResult.mType = HitTestResult.IMAGE_TYPE;
- mInitialHitTestResult.mExtra = hit.mImageUrl;
- } else if (hit.mEditable) {
- mInitialHitTestResult.mType = HitTestResult.EDIT_TEXT_TYPE;
- }
- }
+ setHitTestResult(hit);
break;
case SAVE_WEBARCHIVE_FINISHED:
@@ -9092,9 +9200,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 +9225,9 @@
}
break;
}
+ case CLEAR_CARET_HANDLE:
+ selectionDone();
+ break;
default:
super.handleMessage(msg);
@@ -9123,10 +9236,154 @@
}
}
+ private void setHitTestResult(WebKitHitTest hit) {
+ if (hit == null) {
+ mInitialHitTestResult = null;
+ } else {
+ mInitialHitTestResult = new HitTestResult();
+ if (hit.mLinkUrl != null) {
+ mInitialHitTestResult.mType = HitTestResult.SRC_ANCHOR_TYPE;
+ mInitialHitTestResult.mExtra = hit.mLinkUrl;
+ if (hit.mImageUrl != null) {
+ mInitialHitTestResult.mType = HitTestResult.SRC_IMAGE_ANCHOR_TYPE;
+ mInitialHitTestResult.mExtra = hit.mImageUrl;
+ }
+ } else if (hit.mImageUrl != null) {
+ mInitialHitTestResult.mType = HitTestResult.IMAGE_TYPE;
+ mInitialHitTestResult.mExtra = hit.mImageUrl;
+ } else if (hit.mEditable) {
+ mInitialHitTestResult.mType = HitTestResult.EDIT_TEXT_TYPE;
+ } else if (hit.mIntentUrl != null) {
+ if (hit.mIntentUrl.startsWith(SCHEME_GEO)) {
+ mInitialHitTestResult.mType = HitTestResult.GEO_TYPE;
+ String substr = hit.mIntentUrl.substring(SCHEME_GEO.length());
+ try {
+ mInitialHitTestResult.mExtra = URLDecoder.decode(substr, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ Log.w(LOGTAG, "Failed to decode GEO URL!", e);
+ mInitialHitTestResult.mType = HitTestResult.UNKNOWN_TYPE;
+ }
+ }
+ }
+ }
+ }
+
+ 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 +9403,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 +9510,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..649e5c9 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -879,6 +879,7 @@
static class WebKitHitTest {
String mLinkUrl;
+ String mIntentUrl;
String mAnchorText;
String mImageUrl;
String mAltDisplayString;
@@ -887,6 +888,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 +920,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;
@@ -1382,21 +1403,12 @@
Process.setThreadPriority(mTid,
Process.THREAD_PRIORITY_BACKGROUND);
pauseTimers();
- if (!JniUtil.useChromiumHttpStack()) {
- WebViewWorker.getHandler().sendEmptyMessage(
- WebViewWorker.MSG_PAUSE_CACHE_TRANSACTION);
- } else {
- nativeCloseIdleConnections(mNativeClass);
- }
+ nativeCloseIdleConnections(mNativeClass);
break;
case RESUME_TIMERS:
Process.setThreadPriority(mTid, mSavedPriority);
resumeTimers();
- if (!JniUtil.useChromiumHttpStack()) {
- WebViewWorker.getHandler().sendEmptyMessage(
- WebViewWorker.MSG_RESUME_CACHE_TRANSACTION);
- }
break;
case ON_PAUSE:
@@ -1470,14 +1482,10 @@
}
case CLEAR_SSL_PREF_TABLE:
- if (JniUtil.useChromiumHttpStack()) {
- // FIXME: This will not work for connections currently in use, as
- // they cache the certificate responses. See http://b/5324235.
- SslCertLookupTable.getInstance().clear();
- nativeCloseIdleConnections(mNativeClass);
- } else {
- Network.getInstance(mContext).clearUserSslPrefTable();
- }
+ // FIXME: This will not work for connections currently in use, as
+ // they cache the certificate responses. See http://b/5324235.
+ SslCertLookupTable.getInstance().clear();
+ nativeCloseIdleConnections(mNativeClass);
break;
case TOUCH_UP:
@@ -2425,14 +2433,6 @@
// called by JNI
private void sendNotifyProgressFinished() {
sendUpdateTextEntry();
- if (!JniUtil.useChromiumHttpStack()) {
- // as CacheManager can behave based on database transaction, we need to
- // call tick() to trigger endTransaction
- WebViewWorker.getHandler().removeMessages(
- WebViewWorker.MSG_CACHE_TRANSACTION_TICKER);
- WebViewWorker.getHandler().sendEmptyMessage(
- WebViewWorker.MSG_CACHE_TRANSACTION_TICKER);
- }
contentDraw();
}
@@ -2812,15 +2812,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/webkit/WebViewDatabase.java b/core/java/android/webkit/WebViewDatabase.java
index 695c154..757a619 100644
--- a/core/java/android/webkit/WebViewDatabase.java
+++ b/core/java/android/webkit/WebViewDatabase.java
@@ -31,9 +31,6 @@
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteStatement;
import android.util.Log;
-import android.webkit.CookieManager.Cookie;
-import android.webkit.CacheManager.CacheResult;
-import android.webkit.JniUtil;
public class WebViewDatabase {
private static final String DATABASE_FILE = "webview.db";
@@ -55,39 +52,25 @@
// 10 -> 11 Drop cookies and cache now managed by the chromium stack,
// and update the form data table to use the new format
// implemented for b/5265606.
- private static final int CACHE_DATABASE_VERSION = 4;
- // 1 -> 2 Add expires String
- // 2 -> 3 Add content-disposition
- // 3 -> 4 Add crossdomain (For x-permitted-cross-domain-policies header)
private static WebViewDatabase mInstance = null;
private static SQLiteDatabase mDatabase = null;
- private static SQLiteDatabase mCacheDatabase = null;
// synchronize locks
- private final Object mCookieLock = new Object();
private final Object mPasswordLock = new Object();
private final Object mFormLock = new Object();
private final Object mHttpAuthLock = new Object();
- // TODO: The Chromium HTTP stack handles cookies independently.
- // We should consider removing the cookies table if and when we switch to
- // the Chromium HTTP stack for good.
private static final String mTableNames[] = {
- "cookies", "password", "formurl", "formdata", "httpauth"
+ "password", "formurl", "formdata", "httpauth"
};
// Table ids (they are index to mTableNames)
- private static final int TABLE_COOKIES_ID = 0;
-
- private static final int TABLE_PASSWORD_ID = 1;
-
- private static final int TABLE_FORMURL_ID = 2;
-
- private static final int TABLE_FORMDATA_ID = 3;
-
- private static final int TABLE_HTTPAUTH_ID = 4;
+ private static final int TABLE_PASSWORD_ID = 0;
+ private static final int TABLE_FORMURL_ID = 1;
+ private static final int TABLE_FORMDATA_ID = 2;
+ private static final int TABLE_HTTPAUTH_ID = 3;
// column id strings for "_id" which can be used by any table
private static final String ID_COL = "_id";
@@ -96,51 +79,9 @@
"_id"
};
- // column id strings for "cookies" table
- private static final String COOKIES_NAME_COL = "name";
-
- private static final String COOKIES_VALUE_COL = "value";
-
- private static final String COOKIES_DOMAIN_COL = "domain";
-
- private static final String COOKIES_PATH_COL = "path";
-
- private static final String COOKIES_EXPIRES_COL = "expires";
-
- private static final String COOKIES_SECURE_COL = "secure";
-
- // column id strings for "cache" table
- private static final String CACHE_URL_COL = "url";
-
- private static final String CACHE_FILE_PATH_COL = "filepath";
-
- private static final String CACHE_LAST_MODIFY_COL = "lastmodify";
-
- private static final String CACHE_ETAG_COL = "etag";
-
- private static final String CACHE_EXPIRES_COL = "expires";
-
- private static final String CACHE_EXPIRES_STRING_COL = "expiresstring";
-
- private static final String CACHE_MIMETYPE_COL = "mimetype";
-
- private static final String CACHE_ENCODING_COL = "encoding";
-
- private static final String CACHE_HTTP_STATUS_COL = "httpstatus";
-
- private static final String CACHE_LOCATION_COL = "location";
-
- private static final String CACHE_CONTENTLENGTH_COL = "contentlength";
-
- private static final String CACHE_CONTENTDISPOSITION_COL = "contentdisposition";
-
- private static final String CACHE_CROSSDOMAIN_COL = "crossdomain";
-
// column id strings for "password" table
private static final String PASSWORD_HOST_COL = "host";
-
private static final String PASSWORD_USERNAME_COL = "username";
-
private static final String PASSWORD_PASSWORD_COL = "password";
// column id strings for "formurl" table
@@ -148,38 +89,15 @@
// column id strings for "formdata" table
private static final String FORMDATA_URLID_COL = "urlid";
-
private static final String FORMDATA_NAME_COL = "name";
-
private static final String FORMDATA_VALUE_COL = "value";
// column id strings for "httpauth" table
private static final String HTTPAUTH_HOST_COL = "host";
-
private static final String HTTPAUTH_REALM_COL = "realm";
-
private static final String HTTPAUTH_USERNAME_COL = "username";
-
private static final String HTTPAUTH_PASSWORD_COL = "password";
- // use InsertHelper to improve insert performance by 40%
- private static DatabaseUtils.InsertHelper mCacheInserter;
- private static int mCacheUrlColIndex;
- private static int mCacheFilePathColIndex;
- private static int mCacheLastModifyColIndex;
- private static int mCacheETagColIndex;
- private static int mCacheExpiresColIndex;
- private static int mCacheExpiresStringColIndex;
- private static int mCacheMimeTypeColIndex;
- private static int mCacheEncodingColIndex;
- private static int mCacheHttpStatusColIndex;
- private static int mCacheLocationColIndex;
- private static int mCacheContentLengthColIndex;
- private static int mCacheContentDispositionColIndex;
- private static int mCacheCrossDomainColIndex;
-
- private static int mCacheTransactionRefcount;
-
// Initially true until the background thread completes.
private boolean mInitialized = false;
@@ -207,11 +125,9 @@
}
initDatabase(context);
- if (JniUtil.useChromiumHttpStack()) {
- context.deleteDatabase(CACHE_DATABASE_FILE);
- } else {
- initCacheDatabase(context);
- }
+ // Before using the Chromium HTTP stack, we stored the WebKit cache in
+ // our own DB. Clean up the DB file if it's still around.
+ context.deleteDatabase(CACHE_DATABASE_FILE);
// Thread done, notify.
mInitialized = true;
@@ -254,83 +170,6 @@
mDatabase.setLockingEnabled(false);
}
- private void initCacheDatabase(Context context) {
- assert !JniUtil.useChromiumHttpStack();
-
- try {
- mCacheDatabase = context.openOrCreateDatabase(
- CACHE_DATABASE_FILE, 0, null);
- } catch (SQLiteException e) {
- // try again by deleting the old db and create a new one
- if (context.deleteDatabase(CACHE_DATABASE_FILE)) {
- mCacheDatabase = context.openOrCreateDatabase(
- CACHE_DATABASE_FILE, 0, null);
- }
- }
- mCacheDatabase.enableWriteAheadLogging();
-
- // mCacheDatabase should not be null,
- // the only case is RequestAPI test has problem to create db
- if (mCacheDatabase == null) {
- mInitialized = true;
- notify();
- return;
- }
-
- if (mCacheDatabase.getVersion() != CACHE_DATABASE_VERSION) {
- mCacheDatabase.beginTransactionNonExclusive();
- try {
- upgradeCacheDatabase();
- bootstrapCacheDatabase();
- mCacheDatabase.setTransactionSuccessful();
- } finally {
- mCacheDatabase.endTransaction();
- }
- // Erase the files from the file system in the
- // case that the database was updated and the
- // there were existing cache content
- CacheManager.removeAllCacheFiles();
- }
-
- // use read_uncommitted to speed up READ
- mCacheDatabase.execSQL("PRAGMA read_uncommitted = true;");
- // as only READ can be called in the
- // non-WebViewWorkerThread, and read_uncommitted is used,
- // we can turn off database lock to use transaction.
- mCacheDatabase.setLockingEnabled(false);
-
- // use InsertHelper for faster insertion
- mCacheInserter =
- new DatabaseUtils.InsertHelper(mCacheDatabase,
- "cache");
- mCacheUrlColIndex = mCacheInserter
- .getColumnIndex(CACHE_URL_COL);
- mCacheFilePathColIndex = mCacheInserter
- .getColumnIndex(CACHE_FILE_PATH_COL);
- mCacheLastModifyColIndex = mCacheInserter
- .getColumnIndex(CACHE_LAST_MODIFY_COL);
- mCacheETagColIndex = mCacheInserter
- .getColumnIndex(CACHE_ETAG_COL);
- mCacheExpiresColIndex = mCacheInserter
- .getColumnIndex(CACHE_EXPIRES_COL);
- mCacheExpiresStringColIndex = mCacheInserter
- .getColumnIndex(CACHE_EXPIRES_STRING_COL);
- mCacheMimeTypeColIndex = mCacheInserter
- .getColumnIndex(CACHE_MIMETYPE_COL);
- mCacheEncodingColIndex = mCacheInserter
- .getColumnIndex(CACHE_ENCODING_COL);
- mCacheHttpStatusColIndex = mCacheInserter
- .getColumnIndex(CACHE_HTTP_STATUS_COL);
- mCacheLocationColIndex = mCacheInserter
- .getColumnIndex(CACHE_LOCATION_COL);
- mCacheContentLengthColIndex = mCacheInserter
- .getColumnIndex(CACHE_CONTENTLENGTH_COL);
- mCacheContentDispositionColIndex = mCacheInserter
- .getColumnIndex(CACHE_CONTENTDISPOSITION_COL);
- mCacheCrossDomainColIndex = mCacheInserter
- .getColumnIndex(CACHE_CROSSDOMAIN_COL);
- }
-
private static void upgradeDatabase() {
upgradeDatabaseToV10();
upgradeDatabaseFromV10ToV11();
@@ -347,14 +186,12 @@
return;
}
- if (JniUtil.useChromiumHttpStack()) {
- // Clear out old java stack cookies - this data is now stored in
- // a separate database managed by the Chrome stack.
- mDatabase.execSQL("DROP TABLE IF EXISTS " + mTableNames[TABLE_COOKIES_ID]);
+ // Clear out old java stack cookies - this data is now stored in
+ // a separate database managed by the Chrome stack.
+ mDatabase.execSQL("DROP TABLE IF EXISTS cookies");
- // Likewise for the old cache table.
- mDatabase.execSQL("DROP TABLE IF EXISTS cache");
- }
+ // Likewise for the old cache table.
+ mDatabase.execSQL("DROP TABLE IF EXISTS cache");
// Update form autocomplete URLs to match new ICS formatting.
Cursor c = mDatabase.query(mTableNames[TABLE_FORMURL_ID], null, null,
@@ -397,8 +234,7 @@
return;
}
- mDatabase.execSQL("DROP TABLE IF EXISTS "
- + mTableNames[TABLE_COOKIES_ID]);
+ mDatabase.execSQL("DROP TABLE IF EXISTS cookies");
mDatabase.execSQL("DROP TABLE IF EXISTS cache");
mDatabase.execSQL("DROP TABLE IF EXISTS "
+ mTableNames[TABLE_FORMURL_ID]);
@@ -409,16 +245,6 @@
mDatabase.execSQL("DROP TABLE IF EXISTS "
+ mTableNames[TABLE_PASSWORD_ID]);
- // cookies
- mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_COOKIES_ID]
- + " (" + ID_COL + " INTEGER PRIMARY KEY, "
- + COOKIES_NAME_COL + " TEXT, " + COOKIES_VALUE_COL
- + " TEXT, " + COOKIES_DOMAIN_COL + " TEXT, "
- + COOKIES_PATH_COL + " TEXT, " + COOKIES_EXPIRES_COL
- + " INTEGER, " + COOKIES_SECURE_COL + " INTEGER" + ");");
- mDatabase.execSQL("CREATE INDEX cookiesIndex ON "
- + mTableNames[TABLE_COOKIES_ID] + " (path)");
-
// formurl
mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_FORMURL_ID]
+ " (" + ID_COL + " INTEGER PRIMARY KEY, " + FORMURL_URL_COL
@@ -449,36 +275,6 @@
+ ") ON CONFLICT REPLACE);");
}
- private static void upgradeCacheDatabase() {
- int oldVersion = mCacheDatabase.getVersion();
- if (oldVersion != 0) {
- Log.i(LOGTAG, "Upgrading cache database from version "
- + oldVersion + " to "
- + CACHE_DATABASE_VERSION + ", which will destroy all old data");
- }
- mCacheDatabase.execSQL("DROP TABLE IF EXISTS cache");
- mCacheDatabase.setVersion(CACHE_DATABASE_VERSION);
- }
-
- private static void bootstrapCacheDatabase() {
- if (mCacheDatabase != null) {
- mCacheDatabase.execSQL("CREATE TABLE cache"
- + " (" + ID_COL + " INTEGER PRIMARY KEY, " + CACHE_URL_COL
- + " TEXT, " + CACHE_FILE_PATH_COL + " TEXT, "
- + CACHE_LAST_MODIFY_COL + " TEXT, " + CACHE_ETAG_COL
- + " TEXT, " + CACHE_EXPIRES_COL + " INTEGER, "
- + CACHE_EXPIRES_STRING_COL + " TEXT, "
- + CACHE_MIMETYPE_COL + " TEXT, " + CACHE_ENCODING_COL
- + " TEXT," + CACHE_HTTP_STATUS_COL + " INTEGER, "
- + CACHE_LOCATION_COL + " TEXT, " + CACHE_CONTENTLENGTH_COL
- + " INTEGER, " + CACHE_CONTENTDISPOSITION_COL + " TEXT, "
- + CACHE_CROSSDOMAIN_COL + " TEXT,"
- + " UNIQUE (" + CACHE_URL_COL + ") ON CONFLICT REPLACE);");
- mCacheDatabase.execSQL("CREATE INDEX cacheUrlIndex ON cache ("
- + CACHE_URL_COL + ")");
- }
- }
-
// Wait for the background initialization thread to complete and check the
// database creation status.
private boolean checkInitialized() {
@@ -516,422 +312,6 @@
}
//
- // cookies functions
- //
-
- /**
- * Get cookies in the format of CookieManager.Cookie inside an ArrayList for
- * a given domain
- *
- * @return ArrayList<Cookie> If nothing is found, return an empty list.
- */
- ArrayList<Cookie> getCookiesForDomain(String domain) {
- ArrayList<Cookie> list = new ArrayList<Cookie>();
- if (domain == null || !checkInitialized()) {
- return list;
- }
-
- synchronized (mCookieLock) {
- final String[] columns = new String[] {
- ID_COL, COOKIES_DOMAIN_COL, COOKIES_PATH_COL,
- COOKIES_NAME_COL, COOKIES_VALUE_COL, COOKIES_EXPIRES_COL,
- COOKIES_SECURE_COL
- };
- final String selection = "(" + COOKIES_DOMAIN_COL
- + " GLOB '*' || ?)";
- Cursor cursor = null;
- try {
- cursor = mDatabase.query(mTableNames[TABLE_COOKIES_ID],
- columns, selection, new String[] { domain }, null, null,
- null);
- if (cursor.moveToFirst()) {
- int domainCol = cursor.getColumnIndex(COOKIES_DOMAIN_COL);
- int pathCol = cursor.getColumnIndex(COOKIES_PATH_COL);
- int nameCol = cursor.getColumnIndex(COOKIES_NAME_COL);
- int valueCol = cursor.getColumnIndex(COOKIES_VALUE_COL);
- int expiresCol = cursor.getColumnIndex(COOKIES_EXPIRES_COL);
- int secureCol = cursor.getColumnIndex(COOKIES_SECURE_COL);
- do {
- Cookie cookie = new Cookie();
- cookie.domain = cursor.getString(domainCol);
- cookie.path = cursor.getString(pathCol);
- cookie.name = cursor.getString(nameCol);
- cookie.value = cursor.getString(valueCol);
- if (cursor.isNull(expiresCol)) {
- cookie.expires = -1;
- } else {
- cookie.expires = cursor.getLong(expiresCol);
- }
- cookie.secure = cursor.getShort(secureCol) != 0;
- cookie.mode = Cookie.MODE_NORMAL;
- list.add(cookie);
- } while (cursor.moveToNext());
- }
- } catch (IllegalStateException e) {
- Log.e(LOGTAG, "getCookiesForDomain", e);
- } finally {
- if (cursor != null) cursor.close();
- }
- return list;
- }
- }
-
- /**
- * Delete cookies which matches (domain, path, name).
- *
- * @param domain If it is null, nothing happens.
- * @param path If it is null, all the cookies match (domain) will be
- * deleted.
- * @param name If it is null, all the cookies match (domain, path) will be
- * deleted.
- */
- void deleteCookies(String domain, String path, String name) {
- if (domain == null || !checkInitialized()) {
- return;
- }
-
- synchronized (mCookieLock) {
- final String where = "(" + COOKIES_DOMAIN_COL + " == ?) AND ("
- + COOKIES_PATH_COL + " == ?) AND (" + COOKIES_NAME_COL
- + " == ?)";
- mDatabase.delete(mTableNames[TABLE_COOKIES_ID], where,
- new String[] { domain, path, name });
- }
- }
-
- /**
- * Add a cookie to the database
- *
- * @param cookie
- */
- void addCookie(Cookie cookie) {
- if (cookie.domain == null || cookie.path == null || cookie.name == null
- || !checkInitialized()) {
- return;
- }
-
- synchronized (mCookieLock) {
- ContentValues cookieVal = new ContentValues();
- cookieVal.put(COOKIES_DOMAIN_COL, cookie.domain);
- cookieVal.put(COOKIES_PATH_COL, cookie.path);
- cookieVal.put(COOKIES_NAME_COL, cookie.name);
- cookieVal.put(COOKIES_VALUE_COL, cookie.value);
- if (cookie.expires != -1) {
- cookieVal.put(COOKIES_EXPIRES_COL, cookie.expires);
- }
- cookieVal.put(COOKIES_SECURE_COL, cookie.secure);
- mDatabase.insert(mTableNames[TABLE_COOKIES_ID], null, cookieVal);
- }
- }
-
- /**
- * Whether there is any cookies in the database
- *
- * @return TRUE if there is cookie.
- */
- boolean hasCookies() {
- synchronized (mCookieLock) {
- return hasEntries(TABLE_COOKIES_ID);
- }
- }
-
- /**
- * Clear cookie database
- */
- void clearCookies() {
- if (!checkInitialized()) {
- return;
- }
-
- synchronized (mCookieLock) {
- mDatabase.delete(mTableNames[TABLE_COOKIES_ID], null, null);
- }
- }
-
- /**
- * Clear session cookies, which means cookie doesn't have EXPIRES.
- */
- void clearSessionCookies() {
- if (!checkInitialized()) {
- return;
- }
-
- final String sessionExpired = COOKIES_EXPIRES_COL + " ISNULL";
- synchronized (mCookieLock) {
- mDatabase.delete(mTableNames[TABLE_COOKIES_ID], sessionExpired,
- null);
- }
- }
-
- /**
- * Clear expired cookies
- *
- * @param now Time for now
- */
- void clearExpiredCookies(long now) {
- if (!checkInitialized()) {
- return;
- }
-
- final String expires = COOKIES_EXPIRES_COL + " <= ?";
- synchronized (mCookieLock) {
- mDatabase.delete(mTableNames[TABLE_COOKIES_ID], expires,
- new String[] { Long.toString(now) });
- }
- }
-
- //
- // cache functions
- //
-
- // only called from WebViewWorkerThread
- boolean startCacheTransaction() {
- if (++mCacheTransactionRefcount == 1) {
- if (!Thread.currentThread().equals(
- WebViewWorker.getHandler().getLooper().getThread())) {
- Log.w(LOGTAG, "startCacheTransaction should be called from "
- + "WebViewWorkerThread instead of from "
- + Thread.currentThread().getName());
- }
- mCacheDatabase.beginTransactionNonExclusive();
- return true;
- }
- return false;
- }
-
- // only called from WebViewWorkerThread
- boolean endCacheTransaction() {
- if (--mCacheTransactionRefcount == 0) {
- if (!Thread.currentThread().equals(
- WebViewWorker.getHandler().getLooper().getThread())) {
- Log.w(LOGTAG, "endCacheTransaction should be called from "
- + "WebViewWorkerThread instead of from "
- + Thread.currentThread().getName());
- }
- try {
- mCacheDatabase.setTransactionSuccessful();
- } finally {
- mCacheDatabase.endTransaction();
- }
- return true;
- }
- return false;
- }
-
- /**
- * Get a cache item.
- *
- * @param url The url
- * @return CacheResult The CacheManager.CacheResult
- */
- CacheResult getCache(String url) {
- assert !JniUtil.useChromiumHttpStack();
-
- if (url == null || !checkInitialized()) {
- return null;
- }
-
- Cursor cursor = null;
- final String query = "SELECT filepath, lastmodify, etag, expires, "
- + "expiresstring, mimetype, encoding, httpstatus, location, contentlength, "
- + "contentdisposition, crossdomain FROM cache WHERE url = ?";
- try {
- cursor = mCacheDatabase.rawQuery(query, new String[] { url });
- if (cursor.moveToFirst()) {
- CacheResult ret = new CacheResult();
- ret.localPath = cursor.getString(0);
- ret.lastModified = cursor.getString(1);
- ret.etag = cursor.getString(2);
- ret.expires = cursor.getLong(3);
- ret.expiresString = cursor.getString(4);
- ret.mimeType = cursor.getString(5);
- ret.encoding = cursor.getString(6);
- ret.httpStatusCode = cursor.getInt(7);
- ret.location = cursor.getString(8);
- ret.contentLength = cursor.getLong(9);
- ret.contentdisposition = cursor.getString(10);
- ret.crossDomain = cursor.getString(11);
- return ret;
- }
- } catch (IllegalStateException e) {
- Log.e(LOGTAG, "getCache", e);
- } finally {
- if (cursor != null) cursor.close();
- }
- return null;
- }
-
- /**
- * Remove a cache item.
- *
- * @param url The url
- */
- void removeCache(String url) {
- assert !JniUtil.useChromiumHttpStack();
-
- if (url == null || !checkInitialized()) {
- return;
- }
-
- mCacheDatabase.execSQL("DELETE FROM cache WHERE url = ?", new String[] { url });
- }
-
- /**
- * Add or update a cache. CACHE_URL_COL is unique in the table.
- *
- * @param url The url
- * @param c The CacheManager.CacheResult
- */
- void addCache(String url, CacheResult c) {
- assert !JniUtil.useChromiumHttpStack();
-
- if (url == null || !checkInitialized()) {
- return;
- }
-
- mCacheInserter.prepareForInsert();
- mCacheInserter.bind(mCacheUrlColIndex, url);
- mCacheInserter.bind(mCacheFilePathColIndex, c.localPath);
- mCacheInserter.bind(mCacheLastModifyColIndex, c.lastModified);
- mCacheInserter.bind(mCacheETagColIndex, c.etag);
- mCacheInserter.bind(mCacheExpiresColIndex, c.expires);
- mCacheInserter.bind(mCacheExpiresStringColIndex, c.expiresString);
- mCacheInserter.bind(mCacheMimeTypeColIndex, c.mimeType);
- mCacheInserter.bind(mCacheEncodingColIndex, c.encoding);
- mCacheInserter.bind(mCacheHttpStatusColIndex, c.httpStatusCode);
- mCacheInserter.bind(mCacheLocationColIndex, c.location);
- mCacheInserter.bind(mCacheContentLengthColIndex, c.contentLength);
- mCacheInserter.bind(mCacheContentDispositionColIndex,
- c.contentdisposition);
- mCacheInserter.bind(mCacheCrossDomainColIndex, c.crossDomain);
- mCacheInserter.execute();
- }
-
- /**
- * Clear cache database
- */
- void clearCache() {
- if (!checkInitialized()) {
- return;
- }
-
- mCacheDatabase.delete("cache", null, null);
- }
-
- boolean hasCache() {
- if (!checkInitialized()) {
- return false;
- }
-
- Cursor cursor = null;
- boolean ret = false;
- try {
- cursor = mCacheDatabase.query("cache", ID_PROJECTION,
- null, null, null, null, null);
- ret = cursor.moveToFirst() == true;
- } catch (IllegalStateException e) {
- Log.e(LOGTAG, "hasCache", e);
- } finally {
- if (cursor != null) cursor.close();
- }
- return ret;
- }
-
- long getCacheTotalSize() {
- if (mCacheDatabase == null) {
- return 0;
- }
- long size = 0;
- Cursor cursor = null;
- final String query = "SELECT SUM(contentlength) as sum FROM cache";
- try {
- cursor = mCacheDatabase.rawQuery(query, null);
- if (cursor.moveToFirst()) {
- size = cursor.getLong(0);
- }
- } catch (IllegalStateException e) {
- Log.e(LOGTAG, "getCacheTotalSize", e);
- } finally {
- if (cursor != null) cursor.close();
- }
- return size;
- }
-
- List<String> trimCache(long amount) {
- ArrayList<String> pathList = new ArrayList<String>(100);
- Cursor cursor = null;
- final String query = "SELECT contentlength, filepath FROM cache ORDER BY expires ASC";
- try {
- cursor = mCacheDatabase.rawQuery(query, null);
- if (cursor.moveToFirst()) {
- int batchSize = 100;
- StringBuilder pathStr = new StringBuilder(20 + 16 * batchSize);
- pathStr.append("DELETE FROM cache WHERE filepath IN (?");
- for (int i = 1; i < batchSize; i++) {
- pathStr.append(", ?");
- }
- pathStr.append(")");
- SQLiteStatement statement = null;
- try {
- statement = mCacheDatabase.compileStatement(
- pathStr.toString());
- // as bindString() uses 1-based index, initialize index to 1
- int index = 1;
- do {
- long length = cursor.getLong(0);
- if (length == 0) {
- continue;
- }
- amount -= length;
- String filePath = cursor.getString(1);
- statement.bindString(index, filePath);
- pathList.add(filePath);
- if (index++ == batchSize) {
- statement.execute();
- statement.clearBindings();
- index = 1;
- }
- } while (cursor.moveToNext() && amount > 0);
- if (index > 1) {
- // there may be old bindings from the previous statement
- // if index is less than batchSize, which is Ok.
- statement.execute();
- }
- } catch (IllegalStateException e) {
- Log.e(LOGTAG, "trimCache SQLiteStatement", e);
- } finally {
- if (statement != null) statement.close();
- }
- }
- } catch (IllegalStateException e) {
- Log.e(LOGTAG, "trimCache Cursor", e);
- } finally {
- if (cursor != null) cursor.close();
- }
- return pathList;
- }
-
- List<String> getAllCacheFileNames() {
- ArrayList<String> pathList = null;
- Cursor cursor = null;
- try {
- cursor = mCacheDatabase.rawQuery("SELECT filepath FROM cache",
- null);
- if (cursor != null && cursor.moveToFirst()) {
- pathList = new ArrayList<String>(cursor.getCount());
- do {
- pathList.add(cursor.getString(0));
- } while (cursor.moveToNext());
- }
- } catch (IllegalStateException e) {
- Log.e(LOGTAG, "getAllCacheFileNames", e);
- } finally {
- if (cursor != null) cursor.close();
- }
- return pathList;
- }
-
- //
// password functions
//
diff --git a/core/java/android/webkit/WebViewWorker.java b/core/java/android/webkit/WebViewWorker.java
deleted file mode 100644
index 6a4ca29..0000000
--- a/core/java/android/webkit/WebViewWorker.java
+++ /dev/null
@@ -1,233 +0,0 @@
-/*
- * 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 android.webkit;
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-
-import android.net.http.Headers;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Message;
-
-/**
- * WebViewWorker executes in a separate thread other than UI and WebViewCore. To
- * avoid blocking UI or WebKit's execution, the caller can send a message to
- * WebViewWorker.getHandler() and it will be handled in the WebViewWorkerThread.
- */
-final class WebViewWorker extends Handler {
-
- private static final String THREAD_NAME = "WebViewWorkerThread";
-
- private static WebViewWorker sWorkerHandler;
-
- private static Map<LoadListener, CacheManager.CacheResult> mCacheResultMap
- = new HashMap<LoadListener, CacheManager.CacheResult>();
-
- /**
- * Package level class to be used while creating a cache entry.
- */
- static class CacheCreateData {
- LoadListener mListener;
- String mUrl;
- String mMimeType;
- int mStatusCode;
- long mPostId;
- Headers mHeaders;
- }
-
- /**
- * Package level class to be used while saving a cache entry.
- */
- static class CacheSaveData {
- LoadListener mListener;
- String mUrl;
- long mPostId;
- }
-
- /**
- * Package level class to be used while updating a cache entry's encoding.
- */
- static class CacheEncoding {
- LoadListener mListener;
- String mEncoding;
- }
-
- /**
- * Package level class to be used while appending data to a cache entry.
- */
- static class CacheData {
- LoadListener mListener;
- ByteArrayBuilder.Chunk mChunk;
- }
-
- static synchronized WebViewWorker getHandler() {
- if (sWorkerHandler == null) {
- HandlerThread thread = new HandlerThread(THREAD_NAME,
- android.os.Process.THREAD_PRIORITY_DEFAULT
- + android.os.Process.THREAD_PRIORITY_LESS_FAVORABLE);
- thread.start();
- sWorkerHandler = new WebViewWorker(thread.getLooper());
- }
- return sWorkerHandler;
- }
-
- private WebViewWorker(Looper looper) {
- super(looper);
- }
-
- // trigger transaction once a minute
- private static final int CACHE_TRANSACTION_TICKER_INTERVAL = 60 * 1000;
-
- private static boolean mCacheTickersBlocked = true;
-
- // message ids
- static final int MSG_ADD_STREAMLOADER = 101;
- static final int MSG_ADD_HTTPLOADER = 102;
- static final int MSG_CREATE_CACHE = 103;
- static final int MSG_UPDATE_CACHE_ENCODING = 104;
- static final int MSG_APPEND_CACHE = 105;
- static final int MSG_SAVE_CACHE = 106;
- static final int MSG_REMOVE_CACHE = 107;
- static final int MSG_TRIM_CACHE = 108;
- static final int MSG_CLEAR_CACHE = 109;
- static final int MSG_CACHE_TRANSACTION_TICKER = 110;
- static final int MSG_PAUSE_CACHE_TRANSACTION = 111;
- static final int MSG_RESUME_CACHE_TRANSACTION = 112;
-
- @Override
- public void handleMessage(Message msg) {
- switch(msg.what) {
- case MSG_ADD_STREAMLOADER: {
- StreamLoader loader = (StreamLoader) msg.obj;
- loader.load();
- break;
- }
- case MSG_ADD_HTTPLOADER: {
- FrameLoader loader = (FrameLoader) msg.obj;
- loader.handleHTTPLoad();
- break;
- }
- case MSG_CREATE_CACHE: {
- assert !JniUtil.useChromiumHttpStack();
- CacheCreateData data = (CacheCreateData) msg.obj;
- CacheManager.CacheResult cache = CacheManager.createCacheFile(
- data.mUrl, data.mStatusCode, data.mHeaders,
- data.mMimeType, data.mPostId, false);
- if (cache != null) {
- mCacheResultMap.put(data.mListener, cache);
- } else {
- mCacheResultMap.remove(data.mListener);
- }
- break;
- }
- case MSG_UPDATE_CACHE_ENCODING: {
- assert !JniUtil.useChromiumHttpStack();
- CacheEncoding data = (CacheEncoding) msg.obj;
- CacheManager.CacheResult cache = mCacheResultMap
- .get(data.mListener);
- if (cache != null) {
- cache.encoding = data.mEncoding;
- }
- break;
- }
- case MSG_APPEND_CACHE: {
- assert !JniUtil.useChromiumHttpStack();
- CacheData data = (CacheData) msg.obj;
- CacheManager.CacheResult cache = mCacheResultMap
- .get(data.mListener);
- if (cache != null) {
- cache.contentLength += data.mChunk.mLength;
- if (cache.contentLength > CacheManager.CACHE_MAX_SIZE) {
- CacheManager.cleanupCacheFile(cache);
- mCacheResultMap.remove(data.mListener);
- } else {
- try {
- cache.outStream.write(data.mChunk.mArray, 0,
- data.mChunk.mLength);
- } catch (IOException e) {
- CacheManager.cleanupCacheFile(cache);
- mCacheResultMap.remove(data.mListener);
- }
- }
- }
- data.mChunk.release();
- break;
- }
- case MSG_SAVE_CACHE: {
- assert !JniUtil.useChromiumHttpStack();
- CacheSaveData data = (CacheSaveData) msg.obj;
- CacheManager.CacheResult cache = mCacheResultMap
- .get(data.mListener);
- if (cache != null) {
- CacheManager.saveCacheFile(data.mUrl, data.mPostId, cache);
- mCacheResultMap.remove(data.mListener);
- }
- break;
- }
- case MSG_REMOVE_CACHE: {
- assert !JniUtil.useChromiumHttpStack();
- LoadListener listener = (LoadListener) msg.obj;
- CacheManager.CacheResult cache = mCacheResultMap.get(listener);
- if (cache != null) {
- CacheManager.cleanupCacheFile(cache);
- mCacheResultMap.remove(listener);
- }
- break;
- }
- case MSG_TRIM_CACHE: {
- assert !JniUtil.useChromiumHttpStack();
- CacheManager.trimCacheIfNeeded();
- break;
- }
- case MSG_CLEAR_CACHE: {
- assert !JniUtil.useChromiumHttpStack();
- CacheManager.clearCache();
- break;
- }
- case MSG_CACHE_TRANSACTION_TICKER: {
- assert !JniUtil.useChromiumHttpStack();
- if (!mCacheTickersBlocked) {
- CacheManager.endTransaction();
- CacheManager.startTransaction();
- sendEmptyMessageDelayed(MSG_CACHE_TRANSACTION_TICKER,
- CACHE_TRANSACTION_TICKER_INTERVAL);
- }
- break;
- }
- case MSG_PAUSE_CACHE_TRANSACTION: {
- assert !JniUtil.useChromiumHttpStack();
- if (CacheManager.disableTransaction()) {
- mCacheTickersBlocked = true;
- removeMessages(MSG_CACHE_TRANSACTION_TICKER);
- }
- break;
- }
- case MSG_RESUME_CACHE_TRANSACTION: {
- assert !JniUtil.useChromiumHttpStack();
- if (CacheManager.enableTransaction()) {
- mCacheTickersBlocked = false;
- sendEmptyMessageDelayed(MSG_CACHE_TRANSACTION_TICKER,
- CACHE_TRANSACTION_TICKER_INTERVAL);
- }
- break;
- }
- }
- }
-}
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index e7bc1e1..5774440 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -1020,6 +1020,7 @@
if (mChoiceMode != CHOICE_MODE_NONE) {
handled = true;
+ boolean checkedStateChanged = false;
if (mChoiceMode == CHOICE_MODE_MULTIPLE ||
(mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode != null)) {
@@ -1042,6 +1043,7 @@
position, id, newValue);
dispatchItemClick = false;
}
+ checkedStateChanged = true;
} else if (mChoiceMode == CHOICE_MODE_SINGLE) {
boolean newValue = !mCheckStates.get(position, false);
if (newValue) {
@@ -1055,11 +1057,12 @@
} else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
mCheckedItemCount = 0;
}
+ checkedStateChanged = true;
}
- mDataChanged = true;
- rememberSyncState();
- requestLayout();
+ if (checkedStateChanged) {
+ updateOnScreenCheckedViews();
+ }
}
if (dispatchItemClick) {
@@ -1070,6 +1073,28 @@
}
/**
+ * Perform a quick, in-place update of the checked or activated state
+ * on all visible item views. This should only be called when a valid
+ * choice mode is active.
+ */
+ private void updateOnScreenCheckedViews() {
+ final int firstPos = mFirstPosition;
+ final int count = getChildCount();
+ final boolean useActivated = getContext().getApplicationInfo().targetSdkVersion
+ >= android.os.Build.VERSION_CODES.HONEYCOMB;
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ final int position = firstPos + i;
+
+ if (child instanceof Checkable) {
+ ((Checkable) child).setChecked(mCheckStates.get(position));
+ } else if (useActivated) {
+ child.setActivated(mCheckStates.get(position));
+ }
+ }
+ }
+
+ /**
* @see #setChoiceMode(int)
*
* @return The current choice mode
@@ -2035,13 +2060,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 +2090,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 +5415,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/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java
index 8f10fff..ef1d7d0 100644
--- a/core/java/android/widget/TimePicker.java
+++ b/core/java/android/widget/TimePicker.java
@@ -213,6 +213,7 @@
button.requestFocus();
mIsAm = !mIsAm;
updateAmPmControl();
+ onTimeChanged();
}
});
} else {
@@ -227,6 +228,7 @@
picker.requestFocus();
mIsAm = !mIsAm;
updateAmPmControl();
+ onTimeChanged();
}
});
mAmPmSpinnerInput = (EditText) mAmPmSpinner.findViewById(R.id.numberpicker_input);
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/ActionBarContextView.java b/core/java/com/android/internal/widget/ActionBarContextView.java
index ed02636..fa16527 100644
--- a/core/java/com/android/internal/widget/ActionBarContextView.java
+++ b/core/java/com/android/internal/widget/ActionBarContextView.java
@@ -405,8 +405,7 @@
View child = mMenuView.getChildAt(i);
child.setScaleY(0);
ObjectAnimator a = ObjectAnimator.ofFloat(child, "scaleY", 0, 1);
- a.setDuration(100);
- a.setStartDelay(j * 70);
+ a.setDuration(300);
b.with(a);
}
}
@@ -432,8 +431,7 @@
View child = mMenuView.getChildAt(i);
child.setScaleY(0);
ObjectAnimator a = ObjectAnimator.ofFloat(child, "scaleY", 0);
- a.setDuration(100);
- a.setStartDelay(i * 70);
+ a.setDuration(300);
b.with(a);
}
}
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/java/com/google/android/mms/ContentType.java b/core/java/com/google/android/mms/ContentType.java
index b066fad..12a1343 100644
--- a/core/java/com/google/android/mms/ContentType.java
+++ b/core/java/com/google/android/mms/ContentType.java
@@ -39,6 +39,7 @@
public static final String IMAGE_GIF = "image/gif";
public static final String IMAGE_WBMP = "image/vnd.wap.wbmp";
public static final String IMAGE_PNG = "image/png";
+ public static final String IMAGE_X_MS_BMP = "image/x-ms-bmp";
public static final String AUDIO_UNSPECIFIED = "audio/*";
public static final String AUDIO_AAC = "audio/aac";
@@ -58,6 +59,7 @@
public static final String AUDIO_X_MPEG = "audio/x-mpeg";
public static final String AUDIO_X_MPG = "audio/x-mpg";
public static final String AUDIO_3GPP = "audio/3gpp";
+ public static final String AUDIO_X_WAV = "audio/x-wav";
public static final String AUDIO_OGG = "application/ogg";
public static final String VIDEO_UNSPECIFIED = "video/*";
@@ -89,6 +91,7 @@
sSupportedContentTypes.add(IMAGE_WBMP);
sSupportedContentTypes.add(IMAGE_PNG);
sSupportedContentTypes.add(IMAGE_JPG);
+ sSupportedContentTypes.add(IMAGE_X_MS_BMP);
//supportedContentTypes.add(IMAGE_SVG); not yet supported.
sSupportedContentTypes.add(AUDIO_AAC);
@@ -106,6 +109,7 @@
sSupportedContentTypes.add(AUDIO_X_MPEG3);
sSupportedContentTypes.add(AUDIO_X_MPEG);
sSupportedContentTypes.add(AUDIO_X_MPG);
+ sSupportedContentTypes.add(AUDIO_X_WAV);
sSupportedContentTypes.add(AUDIO_3GPP);
sSupportedContentTypes.add(AUDIO_OGG);
@@ -127,6 +131,7 @@
sSupportedImageTypes.add(IMAGE_WBMP);
sSupportedImageTypes.add(IMAGE_PNG);
sSupportedImageTypes.add(IMAGE_JPG);
+ sSupportedImageTypes.add(IMAGE_X_MS_BMP);
// add supported audio types
sSupportedAudioTypes.add(AUDIO_AAC);
@@ -145,6 +150,7 @@
sSupportedAudioTypes.add(AUDIO_X_MPEG3);
sSupportedAudioTypes.add(AUDIO_X_MPEG);
sSupportedAudioTypes.add(AUDIO_X_MPG);
+ sSupportedAudioTypes.add(AUDIO_X_WAV);
sSupportedAudioTypes.add(AUDIO_3GPP);
sSupportedAudioTypes.add(AUDIO_OGG);
diff --git a/core/java/com/google/android/mms/pdu/PduComposer.java b/core/java/com/google/android/mms/pdu/PduComposer.java
index 8940945..d426f89 100644
--- a/core/java/com/google/android/mms/pdu/PduComposer.java
+++ b/core/java/com/google/android/mms/pdu/PduComposer.java
@@ -835,9 +835,7 @@
appendOctet(PduHeaders.CONTENT_TYPE);
// Message body
- makeMessageBody();
-
- return PDU_COMPOSE_SUCCESS; // Composing the message is OK
+ return makeMessageBody();
}
/**
diff --git a/core/java/com/google/android/mms/pdu/PduPersister.java b/core/java/com/google/android/mms/pdu/PduPersister.java
index 8d57e5d..7037b61 100644
--- a/core/java/com/google/android/mms/pdu/PduPersister.java
+++ b/core/java/com/google/android/mms/pdu/PduPersister.java
@@ -783,7 +783,7 @@
Log.v(TAG, "Saving data to: " + uri);
}
- byte[] buffer = new byte[256];
+ byte[] buffer = new byte[8192];
for (int len = 0; (len = is.read(buffer)) != -1; ) {
os.write(buffer, 0, len);
}
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 78e8df3..c6d3cee 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -185,6 +185,7 @@
libcore/include
LOCAL_SHARED_LIBRARIES := \
+ libandroidfw \
libexpat \
libnativehelper \
libcutils \
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..bb175e7 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..58df147 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 externamente"</string>
+ <string name="permdesc_accessContentProvidersExternally" msgid="4544346486697853685">"Permite acceder a los proveedores de contenido desde la interfaz. 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..159d504 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. Nikoli naj ne bi bilo 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..4cb3a3a 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -337,9 +337,9 @@
<string name="permdesc_readProfile" product="default" msgid="94520753797630679">"Inaruhusu programu kusoma maelezo mafupi ya kibinafsi yaliyohifadhiwa kwenye kifaa chako, kama vile jina lako na taarifa ya kuwasiliana. Hii ina maanisha programu inaweza kukutambua na kutuma taarifa yako fupi ya kibinafsi kwa wengine."</string>
<string name="permlab_writeProfile" msgid="4679878325177177400">"andika kwenye data ya maelezo yako mafupi"</string>
<string name="permdesc_writeProfile" product="default" msgid="4637366723793045603">"Inaruhusu programu kubadilisha au kuongeza taarifa fupi ya kibinafsi iliyohifadhiwa katika kifaa chako, kama vile jina lako na maelezo ya kuwasiliana. Hii ina maanisha kwamba programu zingine zinaweza kukutambua na kutuma taarifa yako fupi kwa wengine."</string>
- <string name="permlab_readSocialStream" product="default" msgid="1268920956152419170">"soma mkondo wako wa kijamii"</string>
+ <string name="permlab_readSocialStream" product="default" msgid="1268920956152419170">"soma mipasho yako wa kijamii"</string>
<string name="permdesc_readSocialStream" product="default" msgid="3419050808547335320">"Inaruhusu programu kufikia na kulandanisha sasisho za kijamii kutoka kwako na marafiki zako. Programu hasidi zinaweza kutumia hii kusoma mawasiliano ya kibinafsi kati yako na marafiki zako kwenye mitandao ya kijamii."</string>
- <string name="permlab_writeSocialStream" product="default" msgid="3504179222493235645">"andika kwa mkondo wako wa kijamii"</string>
+ <string name="permlab_writeSocialStream" product="default" msgid="3504179222493235645">"andika kwa mipasho yako wa kijamii"</string>
<string name="permdesc_writeSocialStream" product="default" msgid="3496277176955721451">"Inaruhusu programu kuonyesha sasisho za kijamii kutoka kwa marafiki zako. Programu hasidi zinaweza kutumia hii kujifanya kuwa rafiki na kukuhadaa kuonyesha nenosiri au taarifa zingine za siri."</string>
<string name="permlab_readCalendar" msgid="5972727560257612398">"soma matukio ya kalenda pamoja na maelezo ya siri"</string>
<string name="permdesc_readCalendar" product="tablet" msgid="2338414551004122687">"Inaruhusu programu kusoma matukio yote ya kalenda yaliyohifadhiwa kwa kompyuta yako kibao pamoja na za marafiki au wafanyakazi wenzako. Programu hasidi zinaweza kutoa maelezo ya kibinafsi kutoka kwa kalenda hizi bila wamiliki kujua."</string>
@@ -639,7 +639,7 @@
<string name="relationTypeParent" msgid="4755635567562925226">"Mzazi"</string>
<string name="relationTypePartner" msgid="7266490285120262781">"Mshirika"</string>
<string name="relationTypeReferredBy" msgid="101573059844135524">"Alipendekezwa na"</string>
- <string name="relationTypeRelative" msgid="1799819930085610271">"Ndugu"</string>
+ <string name="relationTypeRelative" msgid="1799819930085610271">"Jamaa"</string>
<string name="relationTypeSister" msgid="1735983554479076481">"Dada"</string>
<string name="relationTypeSpouse" msgid="394136939428698117">"Mwenzi wa Ndoa"</string>
<string name="sipAddressTypeCustom" msgid="2473580593111590945">"Maalum"</string>
@@ -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 kamwe 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..282fb91 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 abahlinzeki bokuqukethwe ngaphandle"</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..9375730 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. -->
@@ -2981,7 +2999,10 @@
<attr name="textColorLink" />
<!-- Makes the cursor visible (the default) or invisible. -->
<attr name="cursorVisible" format="boolean" />
- <!-- Makes the TextView be at most this many lines tall. -->
+ <!-- Makes the TextView be at most this many lines tall.
+
+ When used on an editable text, the <code>inputType</code> attribute's value must be
+ combined with the <code>textMultiLine</code> flag for the maxLines attribute to apply. -->
<attr name="maxLines" format="integer" min="0" />
<!-- Makes the TextView be at most this many pixels tall. -->
<attr name="maxHeight" />
@@ -2991,7 +3012,10 @@
You could get the same effect by specifying this number in the
layout parameters. -->
<attr name="height" format="dimension" />
- <!-- Makes the TextView be at least this many lines tall. -->
+ <!-- Makes the TextView be at least this many lines tall.
+
+ When used on an editable text, the <code>inputType</code> attribute's value must be
+ combined with the <code>textMultiLine</code> flag for the minLines attribute to apply. -->
<attr name="minLines" format="integer" min="0" />
<!-- Makes the TextView be at least this many pixels tall. -->
<attr name="minHeight" />
@@ -3022,21 +3046,20 @@
<!-- Constrains the text to a single horizontally scrolling line
instead of letting it wrap onto multiple lines, and advances
focus instead of inserting a newline when you press the
- enter key. Note: for editable text views, it is better
- to control this using the textMultiLine flag in the inputType
- attribute. (If both singleLine and inputType are supplied,
- the inputType flags will override the value of singleLine.)
- {@deprecated This attribute is deprecated and is replaced by the textMultiLine flag
- in the inputType attribute. Use caution when altering existing layouts, as the
- default value of singeLine is false (multi-line mode), but if you specify any
- value for inputType, the default is single-line mode. (If both singleLine and
- inputType attributes are found, the inputType flags will override the value of
- singleLine.) } -->
+ enter key.
+
+ The default value is false (multi-line wrapped text mode) for non-editable text, but if
+ you specify any value for inputType, the default is true (single-line input field mode).
+
+ {@deprecated This attribute is deprecated. Use <code>maxLines</code> instead to change
+ the layout of a static text, and use the <code>textMultiLine</code> flag in the
+ inputType attribute instead for editable text views (if both singleLine and inputType
+ are supplied, the inputType flags will override the value of singleLine). } -->
<attr name="singleLine" format="boolean" />
<!-- Specifies whether the TextView is enabled or not. {@deprecated Use state_enabled instead}. -->
<attr name="enabled" format="boolean" />
<!-- If the text is selectable, select it all when the view takes
- focus instead of moving the cursor to the start or end. -->
+ focus. -->
<attr name="selectAllOnFocus" format="boolean" />
<!-- Leave enough room for ascenders and descenders instead of
using the font ascent and descent strictly. (Normally true). -->
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..0950bdb 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" />
@@ -1537,6 +1542,21 @@
<java-symbol type="drawable" name="ic_volume" />
<java-symbol type="drawable" name="stat_notify_sim_toolkit" />
+ <!-- From maps library -->
+ <java-symbol type="array" name="maps_starting_lat_lng" />
+ <java-symbol type="array" name="maps_starting_zoom" />
+ <java-symbol type="attr" name="mapViewStyle" />
+ <java-symbol type="attr" name="state_focused" />
+ <java-symbol type="attr" name="state_selected" />
+ <java-symbol type="attr" name="state_pressed" />
+ <java-symbol type="drawable" name="compass_arrow" />
+ <java-symbol type="drawable" name="compass_base" />
+ <java-symbol type="drawable" name="ic_maps_indicator_current_position_anim" />
+ <java-symbol type="drawable" name="loading_tile_android" />
+ <java-symbol type="drawable" name="maps_google_logo" />
+ <java-symbol type="drawable" name="no_tile_256" />
+ <java-symbol type="drawable" name="reticle" />
+
<!-- From PinyinIME(!!!) -->
<java-symbol type="string" name="inputMethod" />
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/bandwidthtests/src/com/android/bandwidthtest/util/ConnectionUtil.java b/core/tests/bandwidthtests/src/com/android/bandwidthtest/util/ConnectionUtil.java
index 7499f68..8d778c4 100644
--- a/core/tests/bandwidthtests/src/com/android/bandwidthtest/util/ConnectionUtil.java
+++ b/core/tests/bandwidthtests/src/com/android/bandwidthtest/util/ConnectionUtil.java
@@ -44,6 +44,8 @@
import com.android.bandwidthtest.NetworkState.StateTransitionDirection;
import com.android.internal.util.AsyncChannel;
+import junit.framework.Assert;
+
import java.io.IOException;
import java.net.UnknownHostException;
import java.util.List;
@@ -453,6 +455,11 @@
} catch (InterruptedException e) {
e.printStackTrace();
}
+ if (mNetworkInfo == null) {
+ Log.v(LOG_TAG, "Do not have networkInfo! Force fetch of network info.");
+ mNetworkInfo = mCM.getActiveNetworkInfo();
+ Assert.assertNotNull(mNetworkInfo);
+ }
if ((mNetworkInfo.getType() != networkType) ||
(mNetworkInfo.getState() != expectedState)) {
Log.v(LOG_TAG, "network state for " + mNetworkInfo.getType() +
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/docs/html/images/training/lint_icon.png b/docs/html/images/training/lint_icon.png
new file mode 100644
index 0000000..118a741
--- /dev/null
+++ b/docs/html/images/training/lint_icon.png
Binary files differ
diff --git a/docs/html/training/improving-layouts/optimizing-layout.jd b/docs/html/training/improving-layouts/optimizing-layout.jd
index 65c8af7..0eaf199 100644
--- a/docs/html/training/improving-layouts/optimizing-layout.jd
+++ b/docs/html/training/improving-layouts/optimizing-layout.jd
@@ -18,7 +18,7 @@
<ol>
<li><a href="#Inspect">Inspect Your Layout</a></li>
<li><a href="#Revise">Revise Your Layout</a></li>
- <li><a href="#Layoutopt">Use Layoutopt</a></li>
+ <li><a href="#Lint">Use Lint</a></li>
</ol>
<!-- other docs (NOT javadocs) -->
@@ -44,7 +44,7 @@
android.widget.GridView}.</p>
<p>In this lesson you'll learn to use <a
-href="{@docRoot}guide/developing/tools/hierarchy-viewer.html">Heirachy Viewer</a> and <a
+href="{@docRoot}guide/developing/tools/hierarchy-viewer.html">Hierarchy Viewer</a> and <a
href="{@docRoot}guide/developing/tools/layoutopt.html">Layoutopt</a> to examine and optimize your
layout.</p>
@@ -53,7 +53,7 @@
<h2 id="Inspect">Inspect Your Layout</h2>
<p>The Android SDK tools include a tool called <a
-href="{@docRoot}guide/developing/tools/hierarchy-viewer.html">Heirachy Viewer</a> that allows
+href="{@docRoot}guide/developing/tools/hierarchy-viewer.html">Hierarchy Viewer</a> that allows
you to analyze your layout while your application is running. Using this tool helps you discover
bottlenecks in the layout performance.</p>
@@ -130,27 +130,28 @@
layout weight is necessary.</p>
-<h2 id="Layoutopt">Use Layoutopt</h2>
+<h2 id="Lint">Use Lint</h2>
-<p>It is always good practice to also run the <a
-href="{@docRoot}guide/developing/tools/layoutopt.html">layoutopt</a> tool on your final layout files
-to search for places in your view hierarchy that may be optimized. Layoutopt is also in your SDK
-{@code tools/} directory and takes a layout directory name or a space-separated list of layout files
-that you'd like to inspect.</p>
+<p>It is always good practice to run the <a href="http://tools.android.com/tips/lint">Lint</a> tool on your layout files to search for possible view hierarchy optimizations. Lint has replaced the Layoutopt tool and has much greater functionality. Some examples of Lint <a
+href="http://tools.android.com/tips/lint-checks">rules</a> are:</p>
-<p>When you run {@code layoutopt} on a layout file, it prints a line number for each issue found, a
-description of the issue, and for some types of issues it also suggests a resolution. For
-example:</p>
+<ul>
+<li>Use compound drawables - A {@link android.widget.LinearLayout} which contains an {@link android.widget.ImageView} and a {@link android.widget.TextView} can be more efficiently handled as a compound drawable.</li>
+<li>Merge root frame - If a {@link android.widget.FrameLayout} is the root of a layout and does not provide background or padding etc, it can be replaced with a merge tag which is slightly more efficient.</li>
+<li>Useless leaf - A layout that has no children or no background can often be removed (since it is invisible) for a flatter and more efficient layout hierarchy.</li>
+<li>Useless parent - A layout with children that has no siblings, is not a {@link android.widget.ScrollView} or a root layout, and does not have a background, can be removed and have its children moved directly into the parent for a flatter and more efficient layout hierarchy.</li>
+<li>Deep layouts - Layouts with too much nesting are bad for performance. Consider using flatter layouts such as {@link android.widget.RelativeLayout} or {@link android.widget.GridLayout} to improve performance. The default maximum depth is 10.</li>
+</ul>
-<pre class="no-pretty-print classic">
-$ layoutopt samples/
-samples/compound.xml
- 7:23 The root-level <FrameLayout/> can be replaced with <merge/>
- 11:21 This LinearLayout layout or its FrameLayout parent is useless
-samples/simple.xml
- 7:7 The root-level <FrameLayout/> can be replaced with <merge/>
-</pre>
+<p>Another benefit of Lint is that it is integrated into the Android Development Tools for Eclipse (ADT 16+). Lint automatically runs whenever you export an APK, edit and save an XML file or use the Layout Editor. To manually force Lint to run press the Lint button in the Eclipse toolbar.</p>
-<p>After you apply the suggested layout optimizations, run Hierarchy Viewer again to inspect the
-performance changes.</p>
+<img src="{@docRoot}images/training/lint_icon.png" alt="" />
+
+<p>When used inside Eclipse, Lint has the ability to automatically fix some issues, provide suggestions for others and jump directly to the offending code for review. If you don’t use Eclipse for your development, Lint can also be run from the command line. More information about Lint is available at <a href="http://tools.android.com/tips/lint">tools.android.com</a>.</p>
+
+
+
+
+
+
diff --git a/graphics/java/android/graphics/Rect.java b/graphics/java/android/graphics/Rect.java
index 0ada1fb..ec911b0 100644
--- a/graphics/java/android/graphics/Rect.java
+++ b/graphics/java/android/graphics/Rect.java
@@ -352,8 +352,7 @@
// check for empty first
return this.left < this.right && this.top < this.bottom
// now check for containment
- && left <= r.left && top <= r.top
- && right >= r.right && bottom >= r.bottom;
+ && left <= r.left && top <= r.top && right >= r.right && bottom >= r.bottom;
}
/**
@@ -375,20 +374,11 @@
* return false and do not change this rectangle.
*/
public boolean intersect(int left, int top, int right, int bottom) {
- if (this.left < right && left < this.right
- && this.top < bottom && top < this.bottom) {
- if (this.left < left) {
- this.left = left;
- }
- if (this.top < top) {
- this.top = top;
- }
- if (this.right > right) {
- this.right = right;
- }
- if (this.bottom > bottom) {
- this.bottom = bottom;
- }
+ if (this.left < right && left < this.right && this.top < bottom && top < this.bottom) {
+ if (this.left < left) this.left = left;
+ if (this.top < top) this.top = top;
+ if (this.right > right) this.right = right;
+ if (this.bottom > bottom) this.bottom = bottom;
return true;
}
return false;
@@ -422,8 +412,7 @@
* false and do not change this rectangle.
*/
public boolean setIntersect(Rect a, Rect b) {
- if (a.left < b.right && b.left < a.right
- && a.top < b.bottom && b.top < a.bottom) {
+ if (a.left < b.right && b.left < a.right && a.top < b.bottom && b.top < a.bottom) {
left = Math.max(a.left, b.left);
top = Math.max(a.top, b.top);
right = Math.min(a.right, b.right);
@@ -448,8 +437,7 @@
* no event is this rectangle modified.
*/
public boolean intersects(int left, int top, int right, int bottom) {
- return this.left < right && left < this.right
- && this.top < bottom && top < this.bottom;
+ return this.left < right && left < this.right && this.top < bottom && top < this.bottom;
}
/**
@@ -463,8 +451,7 @@
* either of the rectangles modified.
*/
public static boolean intersects(Rect a, Rect b) {
- return a.left < b.right && b.left < a.right
- && a.top < b.bottom && b.top < a.bottom;
+ return a.left < b.right && b.left < a.right && a.top < b.bottom && b.top < a.bottom;
}
/**
@@ -480,14 +467,10 @@
public void union(int left, int top, int right, int bottom) {
if ((left < right) && (top < bottom)) {
if ((this.left < this.right) && (this.top < this.bottom)) {
- if (this.left > left)
- this.left = left;
- if (this.top > top)
- this.top = top;
- if (this.right < right)
- this.right = right;
- if (this.bottom < bottom)
- this.bottom = bottom;
+ if (this.left > left) this.left = left;
+ if (this.top > top) this.top = top;
+ if (this.right < right) this.right = right;
+ if (this.bottom < bottom) this.bottom = bottom;
} else {
this.left = left;
this.top = top;
diff --git a/graphics/jni/Android.mk b/graphics/jni/Android.mk
index 652189f..ca69e1e 100644
--- a/graphics/jni/Android.mk
+++ b/graphics/jni/Android.mk
@@ -6,6 +6,7 @@
LOCAL_SHARED_LIBRARIES := \
libandroid_runtime \
+ libandroidfw \
libnativehelper \
libRS \
libcutils \
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..e5416e4 100644
--- a/include/media/stagefright/foundation/AMessage.h
+++ b/include/media/stagefright/foundation/AMessage.h
@@ -25,6 +25,7 @@
namespace android {
+struct ABuffer;
struct AString;
struct Parcel;
@@ -50,6 +51,7 @@
void setPointer(const char *name, void *value);
void setString(const char *name, const char *s, ssize_t len = -1);
void setObject(const char *name, const sp<RefBase> &obj);
+ void setBuffer(const char *name, const sp<ABuffer> &buffer);
void setMessage(const char *name, const sp<AMessage> &obj);
void setRect(
@@ -64,6 +66,7 @@
bool findPointer(const char *name, void **value) const;
bool findString(const char *name, AString *value) const;
bool findObject(const char *name, sp<RefBase> *obj) const;
+ bool findBuffer(const char *name, sp<ABuffer> *buffer) const;
bool findMessage(const char *name, sp<AMessage> *obj) const;
bool findRect(
@@ -90,10 +93,6 @@
AString debugString(int32_t indent = 0) const;
-protected:
- virtual ~AMessage();
-
-private:
enum Type {
kTypeInt32,
kTypeInt64,
@@ -105,8 +104,16 @@
kTypeObject,
kTypeMessage,
kTypeRect,
+ kTypeBuffer,
};
+ 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 +138,7 @@
};
enum {
- kMaxNumItems = 16
+ kMaxNumItems = 32
};
Item mItems[kMaxNumItems];
size_t mNumItems;
@@ -140,6 +147,9 @@
void freeItem(Item *item);
const Item *findItem(const char *name, Type type) const;
+ void setObjectInternal(
+ const char *name, const sp<RefBase> &obj, Type type);
+
DISALLOW_EVIL_CONSTRUCTORS(AMessage);
};
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/include/utils/KeyedVector.h b/include/utils/KeyedVector.h
index fcc3bcf..85535bd 100644
--- a/include/utils/KeyedVector.h
+++ b/include/utils/KeyedVector.h
@@ -122,7 +122,7 @@
template<typename KEY, typename VALUE> inline
const VALUE& KeyedVector<KEY,VALUE>::valueFor(const KEY& key) const {
- ssize_t i = indexOfKey(key);
+ ssize_t i = this->indexOfKey(key);
assert(i>=0);
return mVector.itemAt(i).value;
}
@@ -139,7 +139,7 @@
template<typename KEY, typename VALUE> inline
VALUE& KeyedVector<KEY,VALUE>::editValueFor(const KEY& key) {
- ssize_t i = indexOfKey(key);
+ ssize_t i = this->indexOfKey(key);
assert(i>=0);
return mVector.editItemAt(i).value;
}
@@ -190,7 +190,7 @@
template<typename KEY, typename VALUE> inline
const VALUE& DefaultKeyedVector<KEY,VALUE>::valueFor(const KEY& key) const {
- ssize_t i = indexOfKey(key);
+ ssize_t i = this->indexOfKey(key);
return i >= 0 ? KeyedVector<KEY,VALUE>::valueAt(i) : mDefault;
}
diff --git a/libs/androidfw/Android.mk b/libs/androidfw/Android.mk
new file mode 100644
index 0000000..c5f8a87
--- /dev/null
+++ b/libs/androidfw/Android.mk
@@ -0,0 +1,113 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+# libandroidfw is partially built for the host (used by build time keymap validation tool)
+# These files are common to host and target builds.
+
+# formerly in libutils
+commonUtilsSources:= \
+ Asset.cpp \
+ AssetDir.cpp \
+ AssetManager.cpp \
+ ObbFile.cpp \
+ ResourceTypes.cpp \
+ StreamingZipInflater.cpp \
+ ZipFileCRO.cpp \
+ ZipFileRO.cpp \
+ ZipUtils.cpp
+
+# formerly in libui
+commonUiSources:= \
+ Input.cpp \
+ Keyboard.cpp \
+ KeyCharacterMap.cpp \
+ KeyLayoutMap.cpp \
+ VirtualKeyMap.cpp
+
+commonSources:= \
+ $(commonUtilsSources) \
+ $(commonUiSources)
+
+# For the host
+# =====================================================
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= $(commonSources)
+
+LOCAL_MODULE:= libandroidfw
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_C_INCLUDES := \
+ external/zlib
+
+include $(BUILD_HOST_STATIC_LIBRARY)
+
+
+# For the device
+# =====================================================
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+ $(commonSources) \
+ BackupData.cpp \
+ BackupHelpers.cpp \
+ InputTransport.cpp
+
+LOCAL_SHARED_LIBRARIES := \
+ liblog \
+ libcutils \
+ libutils \
+ libbinder \
+ libskia \
+ libz
+
+LOCAL_C_INCLUDES := \
+ external/skia/include/core \
+ external/icu4c/common \
+ external/zlib
+
+LOCAL_MODULE:= libandroidfw
+
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_SHARED_LIBRARY)
+
+
+ifeq ($(TARGET_OS),linux)
+include $(CLEAR_VARS)
+LOCAL_C_INCLUDES += \
+ external/skia/include/core \
+ external/zlib \
+ external/icu4c/common \
+ bionic/libc/private
+LOCAL_LDLIBS := -lrt -ldl -lpthread
+LOCAL_MODULE := libandroidfw
+LOCAL_SRC_FILES := $(commonUtilsSources) BackupData.cpp BackupHelpers.cpp
+include $(BUILD_STATIC_LIBRARY)
+endif
+
+
+# Include subdirectory makefiles
+# ============================================================
+
+# If we're building with ONE_SHOT_MAKEFILE (mm, mmm), then what the framework
+# team really wants is to build the stuff defined by this makefile.
+ifeq (,$(ONE_SHOT_MAKEFILE))
+include $(call first-makefiles-under,$(LOCAL_PATH))
+endif
diff --git a/libs/utils/Asset.cpp b/libs/androidfw/Asset.cpp
similarity index 100%
rename from libs/utils/Asset.cpp
rename to libs/androidfw/Asset.cpp
diff --git a/libs/utils/AssetDir.cpp b/libs/androidfw/AssetDir.cpp
similarity index 100%
rename from libs/utils/AssetDir.cpp
rename to libs/androidfw/AssetDir.cpp
diff --git a/libs/utils/AssetManager.cpp b/libs/androidfw/AssetManager.cpp
similarity index 100%
rename from libs/utils/AssetManager.cpp
rename to libs/androidfw/AssetManager.cpp
diff --git a/libs/utils/BackupData.cpp b/libs/androidfw/BackupData.cpp
similarity index 100%
rename from libs/utils/BackupData.cpp
rename to libs/androidfw/BackupData.cpp
diff --git a/libs/utils/BackupHelpers.cpp b/libs/androidfw/BackupHelpers.cpp
similarity index 100%
rename from libs/utils/BackupHelpers.cpp
rename to libs/androidfw/BackupHelpers.cpp
diff --git a/libs/ui/Input.cpp b/libs/androidfw/Input.cpp
similarity index 100%
rename from libs/ui/Input.cpp
rename to libs/androidfw/Input.cpp
diff --git a/libs/ui/InputTransport.cpp b/libs/androidfw/InputTransport.cpp
similarity index 100%
rename from libs/ui/InputTransport.cpp
rename to libs/androidfw/InputTransport.cpp
diff --git a/libs/ui/KeyCharacterMap.cpp b/libs/androidfw/KeyCharacterMap.cpp
similarity index 100%
rename from libs/ui/KeyCharacterMap.cpp
rename to libs/androidfw/KeyCharacterMap.cpp
diff --git a/libs/ui/KeyLayoutMap.cpp b/libs/androidfw/KeyLayoutMap.cpp
similarity index 100%
rename from libs/ui/KeyLayoutMap.cpp
rename to libs/androidfw/KeyLayoutMap.cpp
diff --git a/libs/ui/Keyboard.cpp b/libs/androidfw/Keyboard.cpp
similarity index 100%
rename from libs/ui/Keyboard.cpp
rename to libs/androidfw/Keyboard.cpp
diff --git a/libs/androidfw/MODULE_LICENSE_APACHE2 b/libs/androidfw/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/libs/androidfw/MODULE_LICENSE_APACHE2
diff --git a/libs/androidfw/NOTICE b/libs/androidfw/NOTICE
new file mode 100644
index 0000000..c5b1efa
--- /dev/null
+++ b/libs/androidfw/NOTICE
@@ -0,0 +1,190 @@
+
+ Copyright (c) 2005-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.
+
+ 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.
+
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
diff --git a/libs/utils/ObbFile.cpp b/libs/androidfw/ObbFile.cpp
similarity index 100%
rename from libs/utils/ObbFile.cpp
rename to libs/androidfw/ObbFile.cpp
diff --git a/libs/utils/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
similarity index 100%
rename from libs/utils/ResourceTypes.cpp
rename to libs/androidfw/ResourceTypes.cpp
diff --git a/libs/utils/StreamingZipInflater.cpp b/libs/androidfw/StreamingZipInflater.cpp
similarity index 100%
rename from libs/utils/StreamingZipInflater.cpp
rename to libs/androidfw/StreamingZipInflater.cpp
diff --git a/libs/ui/VirtualKeyMap.cpp b/libs/androidfw/VirtualKeyMap.cpp
similarity index 100%
rename from libs/ui/VirtualKeyMap.cpp
rename to libs/androidfw/VirtualKeyMap.cpp
diff --git a/libs/utils/ZipFileCRO.cpp b/libs/androidfw/ZipFileCRO.cpp
similarity index 100%
rename from libs/utils/ZipFileCRO.cpp
rename to libs/androidfw/ZipFileCRO.cpp
diff --git a/libs/utils/ZipFileRO.cpp b/libs/androidfw/ZipFileRO.cpp
similarity index 100%
rename from libs/utils/ZipFileRO.cpp
rename to libs/androidfw/ZipFileRO.cpp
diff --git a/libs/utils/ZipUtils.cpp b/libs/androidfw/ZipUtils.cpp
similarity index 100%
rename from libs/utils/ZipUtils.cpp
rename to libs/androidfw/ZipUtils.cpp
diff --git a/libs/androidfw/tests/Android.mk b/libs/androidfw/tests/Android.mk
new file mode 100644
index 0000000..d85009b
--- /dev/null
+++ b/libs/androidfw/tests/Android.mk
@@ -0,0 +1,47 @@
+# Build the unit tests.
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+# Build the unit tests.
+test_src_files := \
+ InputChannel_test.cpp \
+ InputEvent_test.cpp \
+ InputPublisherAndConsumer_test.cpp \
+ ObbFile_test.cpp \
+ ZipFileRO_test.cpp
+
+shared_libraries := \
+ libandroidfw \
+ libcutils \
+ libutils \
+ libbinder \
+ libui \
+ libstlport \
+ libskia
+
+static_libraries := \
+ libgtest \
+ libgtest_main
+
+c_includes := \
+ bionic \
+ bionic/libstdc++/include \
+ external/gtest/include \
+ external/stlport/stlport \
+ external/skia/include/core
+
+module_tags := eng tests
+
+$(foreach file,$(test_src_files), \
+ $(eval include $(CLEAR_VARS)) \
+ $(eval LOCAL_SHARED_LIBRARIES := $(shared_libraries)) \
+ $(eval LOCAL_STATIC_LIBRARIES := $(static_libraries)) \
+ $(eval LOCAL_C_INCLUDES := $(c_includes)) \
+ $(eval LOCAL_SRC_FILES := $(file)) \
+ $(eval LOCAL_MODULE := $(notdir $(file:%.cpp=%))) \
+ $(eval LOCAL_MODULE_TAGS := $(module_tags)) \
+ $(eval include $(BUILD_EXECUTABLE)) \
+)
+
+# Build the manual test programs.
+include $(call all-makefiles-under, $(LOCAL_PATH))
diff --git a/libs/ui/tests/InputChannel_test.cpp b/libs/androidfw/tests/InputChannel_test.cpp
similarity index 100%
rename from libs/ui/tests/InputChannel_test.cpp
rename to libs/androidfw/tests/InputChannel_test.cpp
diff --git a/libs/ui/tests/InputEvent_test.cpp b/libs/androidfw/tests/InputEvent_test.cpp
similarity index 100%
rename from libs/ui/tests/InputEvent_test.cpp
rename to libs/androidfw/tests/InputEvent_test.cpp
diff --git a/libs/ui/tests/InputPublisherAndConsumer_test.cpp b/libs/androidfw/tests/InputPublisherAndConsumer_test.cpp
similarity index 100%
rename from libs/ui/tests/InputPublisherAndConsumer_test.cpp
rename to libs/androidfw/tests/InputPublisherAndConsumer_test.cpp
diff --git a/libs/utils/tests/ObbFile_test.cpp b/libs/androidfw/tests/ObbFile_test.cpp
similarity index 100%
rename from libs/utils/tests/ObbFile_test.cpp
rename to libs/androidfw/tests/ObbFile_test.cpp
diff --git a/libs/utils/tests/ZipFileRO_test.cpp b/libs/androidfw/tests/ZipFileRO_test.cpp
similarity index 100%
rename from libs/utils/tests/ZipFileRO_test.cpp
rename to libs/androidfw/tests/ZipFileRO_test.cpp
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/rs/driver/rsdBcc.cpp b/libs/rs/driver/rsdBcc.cpp
index bec6ffff..dd78684 100644
--- a/libs/rs/driver/rsdBcc.cpp
+++ b/libs/rs/driver/rsdBcc.cpp
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-
#include "rsdCore.h"
#include "rsdBcc.h"
#include "rsdRuntime.h"
@@ -30,7 +29,6 @@
#include "libdex/ZipArchive.h"
}
-
using namespace android;
using namespace android::renderscript;
@@ -45,6 +43,7 @@
bcinfo::MetadataExtractor *ME;
InvokeFunc_t *mInvokeFunctions;
+ ForEachFunc_t *mForEachFunctions;
void ** mFieldAddress;
bool * mFieldIsObject;
const uint32_t *mExportForEachSignatureList;
@@ -162,8 +161,16 @@
}
exportForEachSignatureCount = drv->ME->getExportForEachSignatureCount();
- rsAssert(exportForEachSignatureCount <= 1);
drv->mExportForEachSignatureList = drv->ME->getExportForEachSignatureList();
+ if (exportForEachSignatureCount > 0) {
+ drv->mForEachFunctions =
+ (ForEachFunc_t*) calloc(exportForEachSignatureCount,
+ sizeof(ForEachFunc_t));
+ bccGetExportForEachList(drv->mBccScript, exportForEachSignatureCount,
+ (void **) drv->mForEachFunctions);
+ } else {
+ drv->mForEachFunctions = NULL;
+ }
// Copy info over to runtime
script->mHal.info.exportedFunctionCount = drv->ME->getExportFuncCount();
@@ -196,6 +203,7 @@
typedef struct {
Context *rsc;
Script *script;
+ ForEachFunc_t kernel;
uint32_t sig;
const Allocation * ain;
Allocation * aout;
@@ -235,7 +243,7 @@
RsdHal * dc = (RsdHal *)mtls->rsc->mHal.drv;
uint32_t sig = mtls->sig;
- outer_foreach_t fn = (outer_foreach_t) mtls->script->mHal.info.root;
+ outer_foreach_t fn = (outer_foreach_t) mtls->kernel;
while (1) {
uint32_t slice = (uint32_t)android_atomic_inc(&mtls->mSliceNum);
uint32_t yStart = mtls->yStart + slice * mtls->mSliceSize;
@@ -265,7 +273,7 @@
RsdHal * dc = (RsdHal *)mtls->rsc->mHal.drv;
uint32_t sig = mtls->sig;
- outer_foreach_t fn = (outer_foreach_t) mtls->script->mHal.info.root;
+ outer_foreach_t fn = (outer_foreach_t) mtls->kernel;
while (1) {
uint32_t slice = (uint32_t)android_atomic_inc(&mtls->mSliceNum);
uint32_t xStart = mtls->xStart + slice * mtls->mSliceSize;
@@ -299,8 +307,8 @@
memset(&mtls, 0, sizeof(mtls));
DrvScript *drv = (DrvScript *)s->mHal.drv;
- // We only support slot 0 (root) at this point in time.
- rsAssert(slot == 0);
+ mtls.kernel = drv->mForEachFunctions[slot];
+ rsAssert(mtls.kernel != NULL);
mtls.sig = 0x1f; // temp fix for old apps, full table in slang_rs_export_foreach.cpp
if (drv->mExportForEachSignatureList) {
mtls.sig = drv->mExportForEachSignatureList[slot];
@@ -391,7 +399,7 @@
uint32_t sig = mtls.sig;
//ALOGE("launch 3");
- outer_foreach_t fn = (outer_foreach_t) mtls.script->mHal.info.root;
+ outer_foreach_t fn = (outer_foreach_t) mtls.kernel;
for (p.ar[0] = mtls.arrayStart; p.ar[0] < mtls.arrayEnd; p.ar[0]++) {
for (p.z = mtls.zStart; p.z < mtls.zEnd; p.z++) {
for (p.y = mtls.yStart; p.y < mtls.yEnd; p.y++) {
@@ -517,6 +525,11 @@
drv->mInvokeFunctions = NULL;
}
+ if (drv->mForEachFunctions) {
+ free(drv->mForEachFunctions);
+ drv->mForEachFunctions = NULL;
+ }
+
delete drv->ME;
drv->ME = NULL;
diff --git a/libs/rs/driver/rsdCore.h b/libs/rs/driver/rsdCore.h
index 168bdf3..05ca13b 100644
--- a/libs/rs/driver/rsdCore.h
+++ b/libs/rs/driver/rsdCore.h
@@ -25,6 +25,7 @@
#include "rsdGL.h"
typedef void (* InvokeFunc_t)(void);
+typedef void (* ForEachFunc_t)(void);
typedef void (*WorkerCallback_t)(void *usr, uint32_t idx);
typedef struct RsdSymbolTableRec {
diff --git a/libs/rs/rsScript.cpp b/libs/rs/rsScript.cpp
index 357dbe3..6a3bd4b 100644
--- a/libs/rs/rsScript.cpp
+++ b/libs/rs/rsScript.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009 The Android Open Source Project
+ * Copyright (C) 2009-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.
@@ -113,7 +113,7 @@
RsAllocation vain, RsAllocation vaout,
const void *params, size_t paramLen) {
Script *s = static_cast<Script *>(vs);
- s->runForEach(rsc,
+ s->runForEach(rsc, slot,
static_cast<const Allocation *>(vain), static_cast<Allocation *>(vaout),
params, paramLen);
diff --git a/libs/rs/rsScript.h b/libs/rs/rsScript.h
index 99dceaf..7879ea6 100644
--- a/libs/rs/rsScript.h
+++ b/libs/rs/rsScript.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009 The Android Open Source Project
+ * Copyright (C) 2009-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.
@@ -74,6 +74,7 @@
virtual bool freeChildren();
virtual void runForEach(Context *rsc,
+ uint32_t slot,
const Allocation * ain,
Allocation * aout,
const void * usr,
diff --git a/libs/rs/rsScriptC.cpp b/libs/rs/rsScriptC.cpp
index b4eb995..79725b97 100644
--- a/libs/rs/rsScriptC.cpp
+++ b/libs/rs/rsScriptC.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009 The Android Open Source Project
+ * Copyright (C) 2009-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.
@@ -128,6 +128,7 @@
void ScriptC::runForEach(Context *rsc,
+ uint32_t slot,
const Allocation * ain,
Allocation * aout,
const void * usr,
@@ -138,7 +139,7 @@
setupGLState(rsc);
setupScript(rsc);
- rsc->mHal.funcs.script.invokeForEach(rsc, this, 0, ain, aout, usr, usrBytes, sc);
+ rsc->mHal.funcs.script.invokeForEach(rsc, this, slot, ain, aout, usr, usrBytes, sc);
}
void ScriptC::Invoke(Context *rsc, uint32_t slot, const void *data, size_t len) {
diff --git a/libs/rs/rsScriptC.h b/libs/rs/rsScriptC.h
index fc4df51..92e1f4f 100644
--- a/libs/rs/rsScriptC.h
+++ b/libs/rs/rsScriptC.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009 The Android Open Source Project
+ * Copyright (C) 2009-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.
@@ -47,6 +47,7 @@
virtual uint32_t run(Context *);
virtual void runForEach(Context *rsc,
+ uint32_t slot,
const Allocation * ain,
Allocation * aout,
const void * usr,
diff --git a/libs/rs/rsScriptC_Lib.cpp b/libs/rs/rsScriptC_Lib.cpp
index 183e207..a5a0fae 100644
--- a/libs/rs/rsScriptC_Lib.cpp
+++ b/libs/rs/rsScriptC_Lib.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009 The Android Open Source Project
+ * Copyright (C) 2009-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.
@@ -157,7 +157,7 @@
Allocation *in, Allocation *out,
const void *usr, uint32_t usrBytes,
const RsScriptCall *call) {
- target->runForEach(rsc, in, out, usr, usrBytes, call);
+ target->runForEach(rsc, /* root slot */ 0, in, out, usr, usrBytes, call);
}
void rsrAllocationSyncAll(Context *rsc, Script *sc, Allocation *a, RsAllocationUsageType usage) {
diff --git a/libs/ui/Android.mk b/libs/ui/Android.mk
index f8b4452..c029291 100644
--- a/libs/ui/Android.mk
+++ b/libs/ui/Android.mk
@@ -13,41 +13,14 @@
# limitations under the License.
LOCAL_PATH:= $(call my-dir)
-
-# libui is partially built for the host (used by build time keymap validation tool)
-# These files are common to host and target builds.
-commonSources:= \
- Input.cpp \
- Keyboard.cpp \
- KeyLayoutMap.cpp \
- KeyCharacterMap.cpp \
- VirtualKeyMap.cpp
-
-# For the host
-# =====================================================
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES:= $(commonSources)
-
-LOCAL_MODULE:= libui
-
-include $(BUILD_HOST_STATIC_LIBRARY)
-
-
-# For the device
-# =====================================================
-
include $(CLEAR_VARS)
LOCAL_SRC_FILES:= \
- $(commonSources) \
EGLUtils.cpp \
FramebufferNativeWindow.cpp \
GraphicBuffer.cpp \
GraphicBufferAllocator.cpp \
GraphicBufferMapper.cpp \
- InputTransport.cpp \
PixelFormat.cpp \
Rect.cpp \
Region.cpp
@@ -56,14 +29,11 @@
libcutils \
libutils \
libEGL \
- libpixelflinger \
- libhardware \
- libhardware_legacy \
- libskia \
- libbinder
+ libhardware
-LOCAL_C_INCLUDES := \
- external/skia/include/core
+ifneq ($(BOARD_FRAMEBUFFER_FORCE_FORMAT),)
+LOCAL_CFLAGS += -DFRAMEBUFFER_FORCE_FORMAT=$(BOARD_FRAMEBUFFER_FORCE_FORMAT)
+endif
LOCAL_MODULE:= libui
diff --git a/libs/ui/FramebufferNativeWindow.cpp b/libs/ui/FramebufferNativeWindow.cpp
index d1dca0c..26d4823 100644
--- a/libs/ui/FramebufferNativeWindow.cpp
+++ b/libs/ui/FramebufferNativeWindow.cpp
@@ -100,6 +100,18 @@
mNumFreeBuffers = NUM_FRAME_BUFFERS;
mBufferHead = mNumBuffers-1;
+ /*
+ * This does not actually change the framebuffer format. It merely
+ * fakes this format to surfaceflinger so that when it creates
+ * framebuffer surfaces it will use this format. It's really a giant
+ * HACK to allow interworking with buggy gralloc+GPU driver
+ * implementations. You should *NEVER* need to set this for shipping
+ * devices.
+ */
+#ifdef FRAMEBUFFER_FORCE_FORMAT
+ *((uint32_t *)&fbDev->format) = FRAMEBUFFER_FORCE_FORMAT;
+#endif
+
for (i = 0; i < mNumBuffers; i++)
{
buffers[i] = new NativeBuffer(
diff --git a/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/libs/ui/tests/Android.mk b/libs/ui/tests/Android.mk
index 700b604..50cad36 100644
--- a/libs/ui/tests/Android.mk
+++ b/libs/ui/tests/Android.mk
@@ -2,47 +2,5 @@
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
-# Build the unit tests.
-test_src_files := \
- InputChannel_test.cpp \
- InputEvent_test.cpp \
- InputPublisherAndConsumer_test.cpp
-
-shared_libraries := \
- libcutils \
- libutils \
- libEGL \
- libbinder \
- libpixelflinger \
- libhardware \
- libhardware_legacy \
- libui \
- libstlport \
- libskia
-
-static_libraries := \
- libgtest \
- libgtest_main
-
-c_includes := \
- bionic \
- bionic/libstdc++/include \
- external/gtest/include \
- external/stlport/stlport \
- external/skia/include/core
-
-module_tags := eng tests
-
-$(foreach file,$(test_src_files), \
- $(eval include $(CLEAR_VARS)) \
- $(eval LOCAL_SHARED_LIBRARIES := $(shared_libraries)) \
- $(eval LOCAL_STATIC_LIBRARIES := $(static_libraries)) \
- $(eval LOCAL_C_INCLUDES := $(c_includes)) \
- $(eval LOCAL_SRC_FILES := $(file)) \
- $(eval LOCAL_MODULE := $(notdir $(file:%.cpp=%))) \
- $(eval LOCAL_MODULE_TAGS := $(module_tags)) \
- $(eval include $(BUILD_EXECUTABLE)) \
-)
-
# Build the manual test programs.
include $(call all-makefiles-under, $(LOCAL_PATH))
diff --git a/libs/utils/Android.mk b/libs/utils/Android.mk
index 24cf504..a96c8e6 100644
--- a/libs/utils/Android.mk
+++ b/libs/utils/Android.mk
@@ -18,9 +18,6 @@
# and once for the device.
commonSources:= \
- Asset.cpp \
- AssetDir.cpp \
- AssetManager.cpp \
BasicHashtable.cpp \
BlobCache.cpp \
BufferedTextOutput.cpp \
@@ -29,14 +26,11 @@
FileMap.cpp \
Flattenable.cpp \
LinearTransform.cpp \
- ObbFile.cpp \
PropertyMap.cpp \
RefBase.cpp \
- ResourceTypes.cpp \
SharedBuffer.cpp \
Static.cpp \
StopWatch.cpp \
- StreamingZipInflater.cpp \
String8.cpp \
String16.cpp \
StringArray.cpp \
@@ -47,9 +41,6 @@
Tokenizer.cpp \
Unicode.cpp \
VectorImpl.cpp \
- ZipFileCRO.cpp \
- ZipFileRO.cpp \
- ZipUtils.cpp \
misc.cpp
@@ -63,7 +54,6 @@
LOCAL_MODULE:= libutils
LOCAL_CFLAGS += -DLIBUTILS_NATIVE=1 $(TOOL_CFLAGS)
-LOCAL_C_INCLUDES += external/zlib
ifeq ($(HOST_OS),windows)
ifeq ($(strip $(USE_CYGWIN),),)
@@ -79,7 +69,6 @@
include $(BUILD_HOST_STATIC_LIBRARY)
-
# For the device
# =====================================================
include $(CLEAR_VARS)
@@ -88,8 +77,6 @@
# we have the common sources, plus some device-specific stuff
LOCAL_SRC_FILES:= \
$(commonSources) \
- BackupData.cpp \
- BackupHelpers.cpp \
Looper.cpp
ifeq ($(TARGET_OS),linux)
@@ -97,14 +84,11 @@
endif
LOCAL_C_INCLUDES += \
- external/zlib \
- external/icu4c/common \
bionic/libc/private
LOCAL_LDLIBS += -lpthread
LOCAL_SHARED_LIBRARIES := \
- libz \
liblog \
libcutils \
libdl \
@@ -113,19 +97,6 @@
LOCAL_MODULE:= libutils
include $(BUILD_SHARED_LIBRARY)
-ifeq ($(TARGET_OS),linux)
-include $(CLEAR_VARS)
-LOCAL_C_INCLUDES += \
- external/zlib \
- external/icu4c/common \
- bionic/libc/private
-LOCAL_LDLIBS := -lrt -ldl -lpthread
-LOCAL_MODULE := libutils
-LOCAL_SRC_FILES := $(commonSources) BackupData.cpp BackupHelpers.cpp
-include $(BUILD_STATIC_LIBRARY)
-endif
-
-
# Include subdirectory makefiles
# ============================================================
diff --git a/libs/utils/tests/Android.mk b/libs/utils/tests/Android.mk
index 58230f4..a6811fc 100644
--- a/libs/utils/tests/Android.mk
+++ b/libs/utils/tests/Android.mk
@@ -7,10 +7,8 @@
BasicHashtable_test.cpp \
BlobCache_test.cpp \
Looper_test.cpp \
- ObbFile_test.cpp \
String8_test.cpp \
Unicode_test.cpp \
- ZipFileRO_test.cpp \
shared_libraries := \
libz \
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..8b2321c 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,263 @@
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::kTypeBuffer:
+ {
+ sp<ABuffer> buffer;
+ CHECK(msg->findBuffer(key, &buffer));
+
+ 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..dec1c08c 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;
@@ -768,7 +768,7 @@
mediaTimeUs / 1E6);
#endif
- reply->setObject("buffer", accessUnit);
+ reply->setBuffer("buffer", accessUnit);
reply->post();
return OK;
@@ -793,10 +793,8 @@
return;
}
- sp<RefBase> obj;
- CHECK(msg->findObject("buffer", &obj));
-
- sp<ABuffer> buffer = static_cast<ABuffer *>(obj.get());
+ sp<ABuffer> buffer;
+ CHECK(msg->findBuffer("buffer", &buffer));
int64_t &skipUntilMediaTimeUs =
audio
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp
index 56c2773..2a51829 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp
@@ -214,8 +214,6 @@
buffer->meta()->setInt32("csd", true);
mCSD.push(buffer);
-
- msg->setObject("csd", buffer);
} else if (meta->findData(kKeyESDS, &type, &data, &size)) {
ESDS esds((const char *)data, size);
CHECK_EQ(esds.InitCheck(), (status_t)OK);
@@ -242,9 +240,8 @@
CHECK(msg->findMessage("reply", &reply));
#if 0
- sp<RefBase> obj;
- CHECK(msg->findObject("buffer", &obj));
- sp<ABuffer> outBuffer = static_cast<ABuffer *>(obj.get());
+ sp<ABuffer> outBuffer;
+ CHECK(msg->findBuffer("buffer", &outBuffer));
#else
sp<ABuffer> outBuffer;
#endif
@@ -253,7 +250,7 @@
outBuffer = mCSD.editItemAt(mCSDIndex++);
outBuffer->meta()->setInt64("timeUs", 0);
- reply->setObject("buffer", outBuffer);
+ reply->setBuffer("buffer", outBuffer);
reply->post();
return;
}
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp
index 15259cb..5738ecb 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp
@@ -60,7 +60,7 @@
const sp<AMessage> ¬ifyConsumed) {
sp<AMessage> msg = new AMessage(kWhatQueueBuffer, id());
msg->setInt32("audio", static_cast<int32_t>(audio));
- msg->setObject("buffer", buffer);
+ msg->setBuffer("buffer", buffer);
msg->setMessage("notifyConsumed", notifyConsumed);
msg->post();
}
@@ -411,9 +411,8 @@
return;
}
- sp<RefBase> obj;
- CHECK(msg->findObject("buffer", &obj));
- sp<ABuffer> buffer = static_cast<ABuffer *>(obj.get());
+ sp<ABuffer> buffer;
+ CHECK(msg->findBuffer("buffer", &buffer));
sp<AMessage> notifyConsumed;
CHECK(msg->findMessage("notifyConsumed", ¬ifyConsumed));
diff --git a/media/libmediaplayerservice/nuplayer/RTSPSource.cpp b/media/libmediaplayerservice/nuplayer/RTSPSource.cpp
index 6eb0d07..4c65b65 100644
--- a/media/libmediaplayerservice/nuplayer/RTSPSource.cpp
+++ b/media/libmediaplayerservice/nuplayer/RTSPSource.cpp
@@ -218,10 +218,8 @@
CHECK(msg->findSize("trackIndex", &trackIndex));
CHECK_LT(trackIndex, mTracks.size());
- sp<RefBase> obj;
- CHECK(msg->findObject("accessUnit", &obj));
-
- sp<ABuffer> accessUnit = static_cast<ABuffer *>(obj.get());
+ sp<ABuffer> accessUnit;
+ CHECK(msg->findBuffer("accessUnit", &accessUnit));
int32_t damaged;
if (accessUnit->meta()->findInt32("damaged", &damaged)
diff --git a/media/libstagefright/ACodec.cpp b/media/libstagefright/ACodec.cpp
index ca44ea3..c91fbe6 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->setBuffer(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();
}
@@ -1417,7 +2142,7 @@
notify->setPointer("buffer-id", info->mBufferID);
info->mData->meta()->clear();
- notify->setObject("buffer", info->mData);
+ notify->setBuffer("buffer", info->mData);
sp<AMessage> reply = new AMessage(kWhatInputBufferFilled, mCodec->id());
reply->setPointer("buffer-id", info->mBufferID);
@@ -1433,18 +2158,26 @@
IOMX::buffer_id bufferID;
CHECK(msg->findPointer("buffer-id", &bufferID));
- sp<RefBase> obj;
+ sp<ABuffer> buffer;
int32_t err = OK;
- if (!msg->findObject("buffer", &obj)) {
+ bool eos = false;
+
+ if (!msg->findBuffer("buffer", &buffer)) {
CHECK(msg->findInt32("err", &err));
ALOGV("[%s] saw error %d instead of an input buffer",
mCodec->mComponentName.c_str(), err);
- obj.clear();
+ buffer.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 +2189,7 @@
switch (mode) {
case KEEP_BUFFERS:
{
- if (buffer == NULL) {
+ if (eos) {
if (!mCodec->mPortEOS[kPortIndexInput]) {
mCodec->mPortEOS[kPortIndexInput] = true;
mCodec->mInputEOSResult = err;
@@ -1467,9 +2200,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 +2211,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 +2228,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 +2247,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 +2328,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 +2347,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->setBuffer("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 +2426,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 +2507,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 +2557,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 +2623,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 +2646,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 +2712,7 @@
"(error 0x%08x)",
err);
- mCodec->signalError();
+ mCodec->signalError(OMX_ErrorUndefined, err);
}
}
@@ -2202,7 +3036,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 +3074,8 @@
////////////////////////////////////////////////////////////////////////////////
ACodec::ExecutingToIdleState::ExecutingToIdleState(ACodec *codec)
- : BaseState(codec) {
+ : BaseState(codec),
+ mComponentNowIdle(false) {
}
bool ACodec::ExecutingToIdleState::onMessageReceived(const sp<AMessage> &msg) {
@@ -2274,6 +3109,7 @@
void ACodec::ExecutingToIdleState::stateEntered() {
ALOGV("[%s] Now Executing->Idle", mCodec->mComponentName.c_str());
+ mComponentNowIdle = false;
mCodec->mSentFormat = false;
}
@@ -2285,6 +3121,8 @@
CHECK_EQ(data1, (OMX_U32)OMX_CommandStateSet);
CHECK_EQ(data2, (OMX_U32)OMX_StateIdle);
+ mComponentNowIdle = true;
+
changeStateIfWeOwnAllBuffers();
return true;
@@ -2303,7 +3141,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..cfb1e29 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 \
@@ -61,20 +63,26 @@
$(TOP)/external/openssl/include \
LOCAL_SHARED_LIBRARIES := \
- libbinder \
- libmedia \
- libutils \
- libcutils \
- libui \
- libsonivox \
- libvorbisidec \
+ libbinder \
+ libmedia \
+ libutils \
+ libcutils \
+ libui \
+ libsonivox \
+ libvorbisidec \
libstagefright_yuv \
libcamera_client \
- libdrmframework \
- libcrypto \
- libssl \
- libgui \
+ libdrmframework \
+ libcrypto \
+ libssl \
+ libgui \
libstagefright_omx \
+ liblog \
+ libicuuc \
+ libicui18n \
+ libz \
+ libdl \
+ libchromium_net \
LOCAL_STATIC_LIBRARIES := \
libstagefright_color_conversion \
@@ -88,51 +96,14 @@
libstagefright_httplive \
libstagefright_id3 \
libFLAC \
+ libstagefright_chromium_http \
-################################################################################
-
-# The following was shamelessly copied from external/webkit/Android.mk and
-# currently must follow the same logic to determine how webkit was built and
-# if it's safe to link against libchromium_net
-
-# See if the user has specified a stack they want to use
-HTTP_STACK = $(HTTP)
-# We default to the Chrome HTTP stack.
-DEFAULT_HTTP = chrome
-ALT_HTTP = android
-
-ifneq ($(HTTP_STACK),chrome)
- ifneq ($(HTTP_STACK),android)
- # No HTTP stack is specified, pickup the one we want as default.
- ifeq ($(USE_ALT_HTTP),true)
- HTTP_STACK = $(ALT_HTTP)
- else
- HTTP_STACK = $(DEFAULT_HTTP)
- endif
- endif
-endif
-
-ifeq ($(HTTP_STACK),chrome)
-
-LOCAL_SHARED_LIBRARIES += \
- liblog \
- libicuuc \
- libicui18n \
- libz \
- libdl \
-
-LOCAL_STATIC_LIBRARIES += \
- libstagefright_chromium_http
-
-LOCAL_SHARED_LIBRARIES += libstlport libchromium_net
+LOCAL_SHARED_LIBRARIES += libstlport
include external/stlport/libstlport.mk
+# TODO: Chromium is always available, so this flag can be removed.
LOCAL_CPPFLAGS += -DCHROMIUM_AVAILABLE=1
-endif # ifeq ($(HTTP_STACK),chrome)
-
-################################################################################
-
LOCAL_SHARED_LIBRARIES += \
libstagefright_enc_common \
libstagefright_avc_common \
diff --git a/media/libstagefright/MPEG2TSWriter.cpp b/media/libstagefright/MPEG2TSWriter.cpp
index 0b4ecbe..f702376 100644
--- a/media/libstagefright/MPEG2TSWriter.cpp
+++ b/media/libstagefright/MPEG2TSWriter.cpp
@@ -244,7 +244,7 @@
sp<AMessage> notify = mNotify->dup();
notify->setInt32("what", kNotifyBuffer);
- notify->setObject("buffer", out);
+ notify->setBuffer("buffer", out);
notify->setInt32("oob", true);
notify->post();
}
@@ -270,7 +270,7 @@
copy->meta()->setInt32("isSync", true);
}
- notify->setObject("buffer", copy);
+ notify->setBuffer("buffer", copy);
notify->post();
}
@@ -351,7 +351,7 @@
sp<AMessage> notify = mNotify->dup();
notify->setInt32("what", kNotifyBuffer);
- notify->setObject("buffer", mAACBuffer);
+ notify->setBuffer("buffer", mAACBuffer);
notify->post();
mAACBuffer.clear();
@@ -614,10 +614,8 @@
++mNumSourcesDone;
} else if (what == SourceInfo::kNotifyBuffer) {
- sp<RefBase> obj;
- CHECK(msg->findObject("buffer", &obj));
-
- sp<ABuffer> buffer = static_cast<ABuffer *>(obj.get());
+ sp<ABuffer> buffer;
+ CHECK(msg->findBuffer("buffer", &buffer));
int32_t oob;
if (msg->findInt32("oob", &oob) && oob) {
diff --git a/media/libstagefright/MediaCodec.cpp b/media/libstagefright/MediaCodec.cpp
new file mode 100644
index 0000000..4acbdbe
--- /dev/null
+++ b/media/libstagefright/MediaCodec.cpp
@@ -0,0 +1,1180 @@
+/*
+ * 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);
+
+ BufferInfo info;
+ info.mBufferID = bufferID;
+ info.mOwnedByClient = false;
+ CHECK(msg->findBuffer(name.c_str(), &info.mData));
+
+ 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<ABuffer> buffer;
+ CHECK(msg->findBuffer("buffer", &buffer));
+
+ 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->setBuffer("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..afd4763
--- /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->setBuffer("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->setBuffer("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->setBuffer("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->setBuffer("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->setBuffer("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/OMXClient.cpp b/media/libstagefright/OMXClient.cpp
index 7a805aa..7cdb793 100644
--- a/media/libstagefright/OMXClient.cpp
+++ b/media/libstagefright/OMXClient.cpp
@@ -335,6 +335,10 @@
}
void OMXClient::disconnect() {
+ if (mOMX.get() != NULL) {
+ mOMX.clear();
+ mOMX = NULL;
+ }
}
} // 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 0a6776ef..9a00186 100644
--- a/media/libstagefright/foundation/AMessage.cpp
+++ b/media/libstagefright/foundation/AMessage.cpp
@@ -19,6 +19,7 @@
#include <ctype.h>
#include "AAtomizer.h"
+#include "ABuffer.h"
#include "ADebug.h"
#include "ALooperRoster.h"
#include "AString.h"
@@ -157,14 +158,23 @@
item->u.stringValue = new AString(s, len < 0 ? strlen(s) : len);
}
-void AMessage::setObject(const char *name, const sp<RefBase> &obj) {
+void AMessage::setObjectInternal(
+ const char *name, const sp<RefBase> &obj, Type type) {
Item *item = allocateItem(name);
- item->mType = kTypeObject;
+ item->mType = type;
if (obj != NULL) { obj->incStrong(this); }
item->u.refValue = obj.get();
}
+void AMessage::setObject(const char *name, const sp<RefBase> &obj) {
+ setObjectInternal(name, obj, kTypeObject);
+}
+
+void AMessage::setBuffer(const char *name, const sp<ABuffer> &buffer) {
+ setObjectInternal(name, sp<RefBase>(buffer), kTypeBuffer);
+}
+
void AMessage::setMessage(const char *name, const sp<AMessage> &obj) {
Item *item = allocateItem(name);
item->mType = kTypeMessage;
@@ -203,6 +213,15 @@
return false;
}
+bool AMessage::findBuffer(const char *name, sp<ABuffer> *buf) const {
+ const Item *item = findItem(name, kTypeBuffer);
+ if (item) {
+ *buf = (ABuffer *)(item->u.refValue);
+ return true;
+ }
+ return false;
+}
+
bool AMessage::findMessage(const char *name, sp<AMessage> *obj) const {
const Item *item = findItem(name, kTypeMessage);
if (item) {
@@ -542,4 +561,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/libstagefright/rtsp/AAMRAssembler.cpp b/media/libstagefright/rtsp/AAMRAssembler.cpp
index 9d72b1f..fb8abc5 100644
--- a/media/libstagefright/rtsp/AAMRAssembler.cpp
+++ b/media/libstagefright/rtsp/AAMRAssembler.cpp
@@ -211,7 +211,7 @@
}
sp<AMessage> msg = mNotifyMsg->dup();
- msg->setObject("access-unit", accessUnit);
+ msg->setBuffer("access-unit", accessUnit);
msg->post();
queue->erase(queue->begin());
diff --git a/media/libstagefright/rtsp/AAVCAssembler.cpp b/media/libstagefright/rtsp/AAVCAssembler.cpp
index ed8b1df..7ea132e 100644
--- a/media/libstagefright/rtsp/AAVCAssembler.cpp
+++ b/media/libstagefright/rtsp/AAVCAssembler.cpp
@@ -345,7 +345,7 @@
mAccessUnitDamaged = false;
sp<AMessage> msg = mNotifyMsg->dup();
- msg->setObject("access-unit", accessUnit);
+ msg->setBuffer("access-unit", accessUnit);
msg->post();
}
diff --git a/media/libstagefright/rtsp/AH263Assembler.cpp b/media/libstagefright/rtsp/AH263Assembler.cpp
index 498295c..ded70fa 100644
--- a/media/libstagefright/rtsp/AH263Assembler.cpp
+++ b/media/libstagefright/rtsp/AH263Assembler.cpp
@@ -166,7 +166,7 @@
mAccessUnitDamaged = false;
sp<AMessage> msg = mNotifyMsg->dup();
- msg->setObject("access-unit", accessUnit);
+ msg->setBuffer("access-unit", accessUnit);
msg->post();
}
diff --git a/media/libstagefright/rtsp/AMPEG4AudioAssembler.cpp b/media/libstagefright/rtsp/AMPEG4AudioAssembler.cpp
index b0c7007..24c2f30 100644
--- a/media/libstagefright/rtsp/AMPEG4AudioAssembler.cpp
+++ b/media/libstagefright/rtsp/AMPEG4AudioAssembler.cpp
@@ -571,7 +571,7 @@
mAccessUnitDamaged = false;
sp<AMessage> msg = mNotifyMsg->dup();
- msg->setObject("access-unit", accessUnit);
+ msg->setBuffer("access-unit", accessUnit);
msg->post();
}
diff --git a/media/libstagefright/rtsp/AMPEG4ElementaryAssembler.cpp b/media/libstagefright/rtsp/AMPEG4ElementaryAssembler.cpp
index 2f2e2c2..687d72b 100644
--- a/media/libstagefright/rtsp/AMPEG4ElementaryAssembler.cpp
+++ b/media/libstagefright/rtsp/AMPEG4ElementaryAssembler.cpp
@@ -368,7 +368,7 @@
mAccessUnitDamaged = false;
sp<AMessage> msg = mNotifyMsg->dup();
- msg->setObject("access-unit", accessUnit);
+ msg->setBuffer("access-unit", accessUnit);
msg->post();
}
diff --git a/media/libstagefright/rtsp/ARTPConnection.cpp b/media/libstagefright/rtsp/ARTPConnection.cpp
index 8c9dd8d..44988a3 100644
--- a/media/libstagefright/rtsp/ARTPConnection.cpp
+++ b/media/libstagefright/rtsp/ARTPConnection.cpp
@@ -639,7 +639,7 @@
void ARTPConnection::injectPacket(int index, const sp<ABuffer> &buffer) {
sp<AMessage> msg = new AMessage(kWhatInjectPacket, id());
msg->setInt32("index", index);
- msg->setObject("buffer", buffer);
+ msg->setBuffer("buffer", buffer);
msg->post();
}
@@ -647,10 +647,8 @@
int32_t index;
CHECK(msg->findInt32("index", &index));
- sp<RefBase> obj;
- CHECK(msg->findObject("buffer", &obj));
-
- sp<ABuffer> buffer = static_cast<ABuffer *>(obj.get());
+ sp<ABuffer> buffer;
+ CHECK(msg->findBuffer("buffer", &buffer));
List<StreamInfo>::iterator it = mStreams.begin();
while (it != mStreams.end()
diff --git a/media/libstagefright/rtsp/ARTPSession.cpp b/media/libstagefright/rtsp/ARTPSession.cpp
index 7a05b88..ba4e33c 100644
--- a/media/libstagefright/rtsp/ARTPSession.cpp
+++ b/media/libstagefright/rtsp/ARTPSession.cpp
@@ -145,10 +145,8 @@
break;
}
- sp<RefBase> obj;
- CHECK(msg->findObject("access-unit", &obj));
-
- sp<ABuffer> accessUnit = static_cast<ABuffer *>(obj.get());
+ sp<ABuffer> accessUnit;
+ CHECK(msg->findBuffer("access-unit", &accessUnit));
uint64_t ntpTime;
CHECK(accessUnit->meta()->findInt64(
diff --git a/media/libstagefright/rtsp/ARTSPConnection.cpp b/media/libstagefright/rtsp/ARTSPConnection.cpp
index 80a010e..539a888 100644
--- a/media/libstagefright/rtsp/ARTSPConnection.cpp
+++ b/media/libstagefright/rtsp/ARTSPConnection.cpp
@@ -612,7 +612,7 @@
if (mObserveBinaryMessage != NULL) {
sp<AMessage> notify = mObserveBinaryMessage->dup();
- notify->setObject("buffer", buffer);
+ notify->setBuffer("buffer", buffer);
notify->post();
} else {
ALOGW("received binary data, but no one cares.");
diff --git a/media/libstagefright/rtsp/ARawAudioAssembler.cpp b/media/libstagefright/rtsp/ARawAudioAssembler.cpp
index 98bee82..0da5dd2 100644
--- a/media/libstagefright/rtsp/ARawAudioAssembler.cpp
+++ b/media/libstagefright/rtsp/ARawAudioAssembler.cpp
@@ -94,7 +94,7 @@
}
sp<AMessage> msg = mNotifyMsg->dup();
- msg->setObject("access-unit", buffer);
+ msg->setBuffer("access-unit", buffer);
msg->post();
queue->erase(queue->begin());
diff --git a/media/libstagefright/rtsp/MyHandler.h b/media/libstagefright/rtsp/MyHandler.h
index 9a7dd70..deee30f 100644
--- a/media/libstagefright/rtsp/MyHandler.h
+++ b/media/libstagefright/rtsp/MyHandler.h
@@ -857,10 +857,8 @@
return;
}
- sp<RefBase> obj;
- CHECK(msg->findObject("access-unit", &obj));
-
- sp<ABuffer> accessUnit = static_cast<ABuffer *>(obj.get());
+ sp<ABuffer> accessUnit;
+ CHECK(msg->findBuffer("access-unit", &accessUnit));
uint32_t seqNum = (uint32_t)accessUnit->int32Data();
@@ -1005,9 +1003,8 @@
case 'biny':
{
- sp<RefBase> obj;
- CHECK(msg->findObject("buffer", &obj));
- sp<ABuffer> buffer = static_cast<ABuffer *>(obj.get());
+ sp<ABuffer> buffer;
+ CHECK(msg->findBuffer("buffer", &buffer));
int32_t index;
CHECK(buffer->meta()->findInt32("index", &index));
@@ -1488,7 +1485,7 @@
sp<AMessage> msg = mNotify->dup();
msg->setInt32("what", kWhatAccessUnit);
msg->setSize("trackIndex", trackIndex);
- msg->setObject("accessUnit", accessUnit);
+ msg->setBuffer("accessUnit", accessUnit);
msg->post();
}
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/native/android/Android.mk b/native/android/Android.mk
index 9940442f..e2c99ee 100644
--- a/native/android/Android.mk
+++ b/native/android/Android.mk
@@ -18,6 +18,7 @@
LOCAL_SHARED_LIBRARIES := \
libcutils \
+ libandroidfw \
libutils \
libbinder \
libui \
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/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index c12a4b7e..8f35afb 100755
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -2285,13 +2285,21 @@
"Laying out navigation bar window: (%d,%d - %d,%d)",
pf.left, pf.top, pf.right, pf.bottom));
}
- } else if (attrs.type == TYPE_SECURE_SYSTEM_OVERLAY
+ } else if ((attrs.type == TYPE_SECURE_SYSTEM_OVERLAY
+ || attrs.type == TYPE_BOOT_PROGRESS)
&& ((fl & FLAG_FULLSCREEN) != 0)) {
// Fullscreen secure system overlays get what they ask for.
pf.left = df.left = mUnrestrictedScreenLeft;
pf.top = df.top = mUnrestrictedScreenTop;
pf.right = df.right = mUnrestrictedScreenLeft+mUnrestrictedScreenWidth;
pf.bottom = df.bottom = mUnrestrictedScreenTop+mUnrestrictedScreenHeight;
+ } else if (attrs.type == TYPE_BOOT_PROGRESS) {
+ // Boot progress screen always covers entire display.
+ pf.left = df.left = cf.left = mUnrestrictedScreenLeft;
+ pf.top = df.top = cf.top = mUnrestrictedScreenTop;
+ pf.right = df.right = cf.right = mUnrestrictedScreenLeft+mUnrestrictedScreenWidth;
+ pf.bottom = df.bottom = cf.bottom
+ = mUnrestrictedScreenTop+mUnrestrictedScreenHeight;
} else {
pf.left = df.left = cf.left = mRestrictedScreenLeft;
pf.top = df.top = cf.top = mRestrictedScreenTop;
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/input/Android.mk b/services/input/Android.mk
index 86c6c8a..159800f 100644
--- a/services/input/Android.mk
+++ b/services/input/Android.mk
@@ -29,6 +29,7 @@
LOCAL_SHARED_LIBRARIES := \
libcutils \
+ libandroidfw \
libutils \
libhardware \
libhardware_legacy \
diff --git a/services/input/tests/Android.mk b/services/input/tests/Android.mk
index 171db3c..8f8c34b 100644
--- a/services/input/tests/Android.mk
+++ b/services/input/tests/Android.mk
@@ -9,6 +9,7 @@
shared_libraries := \
libcutils \
+ libandroidfw \
libutils \
libhardware \
libhardware_legacy \
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/DimAnimator.java b/services/java/com/android/server/wm/DimAnimator.java
index a3293e8..a9d4e01 100644
--- a/services/java/com/android/server/wm/DimAnimator.java
+++ b/services/java/com/android/server/wm/DimAnimator.java
@@ -180,7 +180,9 @@
public void printTo(String prefix, PrintWriter pw) {
pw.print(prefix);
- pw.print("mDimSurface="); pw.println(mDimSurface);
+ pw.print("mDimSurface="); pw.print(mDimSurface);
+ pw.print(" "); pw.print(mLastDimWidth); pw.print(" x ");
+ pw.println(mLastDimHeight);
pw.print(prefix);
pw.print("mDimShown="); pw.print(mDimShown);
pw.print(" current="); pw.print(mDimCurrentAlpha);
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index 80ef0e6..21cb3e8 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;
@@ -8566,6 +8570,11 @@
if (mDimAnimator == null) {
mDimAnimator = new DimAnimator(mFxSession);
}
+ if (attrs.type == WindowManager.LayoutParams.TYPE_BOOT_PROGRESS) {
+ mDimAnimator.show(mCurDisplayWidth, mCurDisplayHeight);
+ } else {
+ mDimAnimator.show(innerDw, innerDh);
+ }
mDimAnimator.show(innerDw, innerDh);
mDimAnimator.updateParameters(mContext.getResources(),
w, currentTime);
@@ -8652,7 +8661,6 @@
boolean focusDisplayed = false;
mInnerFields.mAnimating = false;
boolean createWatermark = false;
- boolean updateRotation = false;
if (mFxSession == null) {
mFxSession = new SurfaceSession();
@@ -8706,22 +8714,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 +8732,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 +8754,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 +8777,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 +9024,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/services/jni/Android.mk b/services/jni/Android.mk
index c63b84df..c02dd36 100644
--- a/services/jni/Android.mk
+++ b/services/jni/Android.mk
@@ -26,6 +26,7 @@
LOCAL_SHARED_LIBRARIES := \
libandroid_runtime \
+ libandroidfw \
libcutils \
libhardware \
libhardware_legacy \
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..621bfa36
--- /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() {
diff --git a/tests/RenderScriptTests/tests/src/com/android/rs/test/RSTestCore.java b/tests/RenderScriptTests/tests/src/com/android/rs/test/RSTestCore.java
index c7bd809..6f56223 100644
--- a/tests/RenderScriptTests/tests/src/com/android/rs/test/RSTestCore.java
+++ b/tests/RenderScriptTests/tests/src/com/android/rs/test/RSTestCore.java
@@ -64,11 +64,6 @@
unitTests = new ArrayList<UnitTest>();
- unitTests.add(new UT_mesh(this, mRes, mCtx));
- unitTests.add(new UT_element(this, mRes, mCtx));
- unitTests.add(new UT_sampler(this, mRes, mCtx));
- unitTests.add(new UT_program_store(this, mRes, mCtx));
- unitTests.add(new UT_program_raster(this, mRes, mCtx));
unitTests.add(new UT_primitives(this, mRes, mCtx));
unitTests.add(new UT_constant(this, mRes, mCtx));
unitTests.add(new UT_vector(this, mRes, mCtx));
@@ -79,10 +74,17 @@
unitTests.add(new UT_alloc(this, mRes, mCtx));
unitTests.add(new UT_refcount(this, mRes, mCtx));
unitTests.add(new UT_foreach(this, mRes, mCtx));
+ unitTests.add(new UT_noroot(this, mRes, mCtx));
unitTests.add(new UT_atomic(this, mRes, mCtx));
unitTests.add(new UT_struct(this, mRes, mCtx));
unitTests.add(new UT_math(this, mRes, mCtx));
+ unitTests.add(new UT_mesh(this, mRes, mCtx));
+ unitTests.add(new UT_element(this, mRes, mCtx));
+ unitTests.add(new UT_sampler(this, mRes, mCtx));
+ unitTests.add(new UT_program_store(this, mRes, mCtx));
+ unitTests.add(new UT_program_raster(this, mRes, mCtx));
unitTests.add(new UT_fp_mad(this, mRes, mCtx));
+
/*
unitTests.add(new UnitTest(null, "<Pass>", 1));
unitTests.add(new UnitTest());
diff --git a/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_foreach.java b/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_foreach.java
index 1d2555e..04e9270 100644
--- a/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_foreach.java
+++ b/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_foreach.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2011 The Android Open Source Project
+ * Copyright (C) 2011-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.
@@ -48,6 +48,9 @@
pRS.setMessageHandler(mRsMessage);
initializeGlobals(pRS, s);
s.forEach_root(A);
+ s.invoke_verify_root();
+ s.forEach_foo(A, A);
+ s.invoke_verify_foo();
s.invoke_foreach_test();
pRS.finish();
waitForMessage();
diff --git a/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_noroot.java b/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_noroot.java
new file mode 100644
index 0000000..c660fc5
--- /dev/null
+++ b/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_noroot.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2011-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.rs.test;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.renderscript.*;
+
+public class UT_noroot extends UnitTest {
+ private Resources mRes;
+ private Allocation A;
+
+ protected UT_noroot(RSTestCore rstc, Resources res, Context ctx) {
+ super(rstc, "ForEach (no root)", ctx);
+ mRes = res;
+ }
+
+ private void initializeGlobals(RenderScript RS, ScriptC_noroot s) {
+ Type.Builder typeBuilder = new Type.Builder(RS, Element.I32(RS));
+ int X = 5;
+ int Y = 7;
+ s.set_dimX(X);
+ s.set_dimY(Y);
+ typeBuilder.setX(X).setY(Y);
+ A = Allocation.createTyped(RS, typeBuilder.create());
+ s.bind_a(A);
+
+ return;
+ }
+
+ public void run() {
+ RenderScript pRS = RenderScript.create(mCtx);
+ ScriptC_noroot s = new ScriptC_noroot(pRS, mRes, R.raw.noroot);
+ pRS.setMessageHandler(mRsMessage);
+ initializeGlobals(pRS, s);
+ s.forEach_foo(A, A);
+ s.invoke_verify_foo();
+ s.invoke_noroot_test();
+ pRS.finish();
+ waitForMessage();
+ pRS.destroy();
+ }
+}
diff --git a/tests/RenderScriptTests/tests/src/com/android/rs/test/foreach.rs b/tests/RenderScriptTests/tests/src/com/android/rs/test/foreach.rs
index 3ba3eef..ac527b5 100644
--- a/tests/RenderScriptTests/tests/src/com/android/rs/test/foreach.rs
+++ b/tests/RenderScriptTests/tests/src/com/android/rs/test/foreach.rs
@@ -3,12 +3,19 @@
int *a;
int dimX;
int dimY;
+static bool failed = false;
void root(int *out, uint32_t x, uint32_t y) {
*out = x + y * dimX;
}
-static bool test_foreach_output() {
+void foo(const int *in, int *out, uint32_t x, uint32_t y) {
+ _RS_ASSERT(*in == (x + y * dimX));
+ *out = 99 + x + y * dimX;
+ _RS_ASSERT(*out == (99 + x + y * dimX));
+}
+
+static bool test_root_output() {
bool failed = false;
int i, j;
@@ -19,19 +26,44 @@
}
if (failed) {
- rsDebug("test_foreach_output FAILED", 0);
+ rsDebug("test_root_output FAILED", 0);
}
else {
- rsDebug("test_foreach_output PASSED", 0);
+ rsDebug("test_root_output PASSED", 0);
}
return failed;
}
-void foreach_test() {
+static bool test_foo_output() {
bool failed = false;
- failed |= test_foreach_output();
+ int i, j;
+ for (j = 0; j < dimY; j++) {
+ for (i = 0; i < dimX; i++) {
+ _RS_ASSERT(a[i + j * dimX] == (99 + i + j * dimX));
+ }
+ }
+
+ if (failed) {
+ rsDebug("test_foo_output FAILED", 0);
+ }
+ else {
+ rsDebug("test_foo_output PASSED", 0);
+ }
+
+ return failed;
+}
+
+void verify_root() {
+ failed |= test_root_output();
+}
+
+void verify_foo() {
+ failed |= test_foo_output();
+}
+
+void foreach_test() {
if (failed) {
rsSendToClientBlocking(RS_MSG_TEST_FAILED);
}
diff --git a/tests/RenderScriptTests/tests/src/com/android/rs/test/noroot.rs b/tests/RenderScriptTests/tests/src/com/android/rs/test/noroot.rs
new file mode 100644
index 0000000..33944aa
--- /dev/null
+++ b/tests/RenderScriptTests/tests/src/com/android/rs/test/noroot.rs
@@ -0,0 +1,44 @@
+#include "shared.rsh"
+
+int *a;
+int dimX;
+int dimY;
+static bool failed = false;
+
+void foo(const int *in, int *out, uint32_t x, uint32_t y) {
+ *out = 99 + x + y * dimX;
+}
+
+static bool test_foo_output() {
+ bool failed = false;
+ int i, j;
+
+ for (j = 0; j < dimY; j++) {
+ for (i = 0; i < dimX; i++) {
+ _RS_ASSERT(a[i + j * dimX] == (99 + i + j * dimX));
+ }
+ }
+
+ if (failed) {
+ rsDebug("test_foo_output FAILED", 0);
+ }
+ else {
+ rsDebug("test_foo_output PASSED", 0);
+ }
+
+ return failed;
+}
+
+void verify_foo() {
+ failed |= test_foo_output();
+}
+
+void noroot_test() {
+ if (failed) {
+ rsSendToClientBlocking(RS_MSG_TEST_FAILED);
+ }
+ else {
+ rsSendToClientBlocking(RS_MSG_TEST_PASSED);
+ }
+}
+
diff --git a/tests/backup/Android.mk b/tests/backup/Android.mk
index 31e2ec5..88794c2 100644
--- a/tests/backup/Android.mk
+++ b/tests/backup/Android.mk
@@ -24,7 +24,7 @@
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE := backup_helper_test
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_SHARED_LIBRARIES := libutils
+LOCAL_SHARED_LIBRARIES := libandroidfw libutils
include $(BUILD_EXECUTABLE)
diff --git a/tools/aapt/Android.mk b/tools/aapt/Android.mk
index cb55a9c..d0a81dc 100644
--- a/tools/aapt/Android.mk
+++ b/tools/aapt/Android.mk
@@ -38,6 +38,7 @@
#LOCAL_WHOLE_STATIC_LIBRARIES :=
LOCAL_STATIC_LIBRARIES := \
libhost \
+ libandroidfw \
libutils \
libcutils \
libexpat \
diff --git a/tools/aapt/XMLNode.cpp b/tools/aapt/XMLNode.cpp
index 95a68d1..9ee6c84 100644
--- a/tools/aapt/XMLNode.cpp
+++ b/tools/aapt/XMLNode.cpp
@@ -21,6 +21,7 @@
const char* const RESOURCES_ROOT_NAMESPACE = "http://schemas.android.com/apk/res/";
const char* const RESOURCES_ANDROID_NAMESPACE = "http://schemas.android.com/apk/res/android";
+const char* const RESOURCES_AUTO_PACKAGE_NAMESPACE = "http://schemas.android.com/apk/res/auto";
const char* const RESOURCES_ROOT_PRV_NAMESPACE = "http://schemas.android.com/apk/prv/res/";
const char* const XLIFF_XMLNS = "urn:oasis:names:tc:xliff:document:1.2";
@@ -44,16 +45,21 @@
}
static const String16 RESOURCES_PREFIX(RESOURCES_ROOT_NAMESPACE);
+static const String16 RESOURCES_PREFIX_AUTO_PACKAGE(RESOURCES_AUTO_PACKAGE_NAMESPACE);
static const String16 RESOURCES_PRV_PREFIX(RESOURCES_ROOT_PRV_NAMESPACE);
static const String16 RESOURCES_TOOLS_NAMESPACE("http://schemas.android.com/tools");
-String16 getNamespaceResourcePackage(String16 namespaceUri, bool* outIsPublic)
+String16 getNamespaceResourcePackage(String16 appPackage, String16 namespaceUri, bool* outIsPublic)
{
//printf("%s starts with %s?\n", String8(namespaceUri).string(),
// String8(RESOURCES_PREFIX).string());
size_t prefixSize;
bool isPublic = true;
- if (namespaceUri.startsWith(RESOURCES_PREFIX)) {
+ if(namespaceUri.startsWith(RESOURCES_PREFIX_AUTO_PACKAGE)) {
+ NOISY(printf("Using default application package: %s -> %s\n", String8(namespaceUri).string(), String8(appPackage).string()));
+ isPublic = true;
+ return appPackage;
+ } else if (namespaceUri.startsWith(RESOURCES_PREFIX)) {
prefixSize = RESOURCES_PREFIX.size();
} else if (namespaceUri.startsWith(RESOURCES_PRV_PREFIX)) {
isPublic = false;
@@ -926,7 +932,7 @@
const attribute_entry& e = mAttributes.itemAt(i);
if (e.ns.size() <= 0) continue;
bool nsIsPublic;
- String16 pkg(getNamespaceResourcePackage(e.ns, &nsIsPublic));
+ String16 pkg(getNamespaceResourcePackage(String16(assets->getPackage()), e.ns, &nsIsPublic));
NOISY(printf("Elem %s %s=\"%s\": namespace(%s) %s ===> %s\n",
String8(getElementName()).string(),
String8(e.name).string(),
diff --git a/tools/layoutlib/bridge/src/android/util/BridgeXmlPullAttributes.java b/tools/layoutlib/bridge/src/android/util/BridgeXmlPullAttributes.java
index 0a3cdc6..6ac5b02 100644
--- a/tools/layoutlib/bridge/src/android/util/BridgeXmlPullAttributes.java
+++ b/tools/layoutlib/bridge/src/android/util/BridgeXmlPullAttributes.java
@@ -18,6 +18,7 @@
import com.android.ide.common.rendering.api.RenderResources;
import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.internal.util.XmlUtils;
import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.BridgeConstants;
import com.android.layoutlib.bridge.android.BridgeContext;
@@ -25,9 +26,6 @@
import org.xmlpull.v1.XmlPullParser;
-import android.util.AttributeSet;
-import android.util.XmlPullAttributes;
-
/**
* A correct implementation of the {@link AttributeSet} interface on top of a XmlPullParser
*/
@@ -80,21 +78,40 @@
return 0;
}
- /*
- * (non-Javadoc)
- * @see android.util.XmlPullAttributes#getAttributeResourceValue(int, int)
- */
@Override
- public int getAttributeResourceValue(int index, int defaultValue) {
- String value = getAttributeValue(index);
+ public int getAttributeListValue(String namespace, String attribute,
+ String[] options, int defaultValue) {
+ String value = getAttributeValue(namespace, attribute);
+ if (value != null) {
+ ResourceValue r = getResourceValue(value);
- return resolveResourceValue(value, defaultValue);
+ if (r != null) {
+ value = r.getValue();
+ }
+
+ return XmlUtils.convertValueToList(value, options, defaultValue);
+ }
+
+ return defaultValue;
}
- /*
- * (non-Javadoc)
- * @see android.util.XmlPullAttributes#getAttributeResourceValue(java.lang.String, java.lang.String, int)
- */
+ @Override
+ public boolean getAttributeBooleanValue(String namespace, String attribute,
+ boolean defaultValue) {
+ String value = getAttributeValue(namespace, attribute);
+ if (value != null) {
+ ResourceValue r = getResourceValue(value);
+
+ if (r != null) {
+ value = r.getValue();
+ }
+
+ return XmlUtils.convertValueToBoolean(value, defaultValue);
+ }
+
+ return defaultValue;
+ }
+
@Override
public int getAttributeResourceValue(String namespace, String attribute, int defaultValue) {
String value = getAttributeValue(namespace, attribute);
@@ -102,12 +119,151 @@
return resolveResourceValue(value, defaultValue);
}
- private int resolveResourceValue(String value, int defaultValue) {
+ @Override
+ public int getAttributeIntValue(String namespace, String attribute,
+ int defaultValue) {
+ String value = getAttributeValue(namespace, attribute);
+ if (value != null) {
+ ResourceValue r = getResourceValue(value);
+
+ if (r != null) {
+ value = r.getValue();
+ }
+
+ return XmlUtils.convertValueToInt(value, defaultValue);
+ }
+
+ return defaultValue;
+ }
+
+ @Override
+ public int getAttributeUnsignedIntValue(String namespace, String attribute,
+ int defaultValue) {
+ String value = getAttributeValue(namespace, attribute);
+ if (value != null) {
+ ResourceValue r = getResourceValue(value);
+
+ if (r != null) {
+ value = r.getValue();
+ }
+
+ return XmlUtils.convertValueToUnsignedInt(value, defaultValue);
+ }
+
+ return defaultValue;
+ }
+
+ @Override
+ public float getAttributeFloatValue(String namespace, String attribute,
+ float defaultValue) {
+ String s = getAttributeValue(namespace, attribute);
+ if (s != null) {
+ ResourceValue r = getResourceValue(s);
+
+ if (r != null) {
+ s = r.getValue();
+ }
+
+ return Float.parseFloat(s);
+ }
+
+ return defaultValue;
+ }
+
+ @Override
+ public int getAttributeListValue(int index,
+ String[] options, int defaultValue) {
+ return XmlUtils.convertValueToList(
+ getAttributeValue(index), options, defaultValue);
+ }
+
+ @Override
+ public boolean getAttributeBooleanValue(int index, boolean defaultValue) {
+ String value = getAttributeValue(index);
+ if (value != null) {
+ ResourceValue r = getResourceValue(value);
+
+ if (r != null) {
+ value = r.getValue();
+ }
+
+ return XmlUtils.convertValueToBoolean(value, defaultValue);
+ }
+
+ return defaultValue;
+ }
+
+ @Override
+ public int getAttributeResourceValue(int index, int defaultValue) {
+ String value = getAttributeValue(index);
+
+ return resolveResourceValue(value, defaultValue);
+ }
+
+ @Override
+ public int getAttributeIntValue(int index, int defaultValue) {
+ String value = getAttributeValue(index);
+ if (value != null) {
+ ResourceValue r = getResourceValue(value);
+
+ if (r != null) {
+ value = r.getValue();
+ }
+
+ return XmlUtils.convertValueToInt(value, defaultValue);
+ }
+
+ return defaultValue;
+ }
+
+ @Override
+ public int getAttributeUnsignedIntValue(int index, int defaultValue) {
+ String value = getAttributeValue(index);
+ if (value != null) {
+ ResourceValue r = getResourceValue(value);
+
+ if (r != null) {
+ value = r.getValue();
+ }
+
+ return XmlUtils.convertValueToUnsignedInt(value, defaultValue);
+ }
+
+ return defaultValue;
+ }
+
+ @Override
+ public float getAttributeFloatValue(int index, float defaultValue) {
+ String s = getAttributeValue(index);
+ if (s != null) {
+ ResourceValue r = getResourceValue(s);
+
+ if (r != null) {
+ s = r.getValue();
+ }
+
+ return Float.parseFloat(s);
+ }
+
+ return defaultValue;
+ }
+
+ // -- private helper methods
+
+ /**
+ * Returns a resolved {@link ResourceValue} from a given value.
+ */
+ private ResourceValue getResourceValue(String value) {
// now look for this particular value
RenderResources resources = mContext.getRenderResources();
- ResourceValue resource = resources.resolveResValue(
- resources.findResValue(value, mPlatformFile));
+ return resources.resolveResValue(resources.findResValue(value, mPlatformFile));
+ }
+ /**
+ * Resolves and return a value to its associated integer.
+ */
+ private int resolveResourceValue(String value, int defaultValue) {
+ ResourceValue resource = getResourceValue(value);
if (resource != null) {
Integer id = null;
if (mPlatformFile || resource.isFramework()) {
@@ -124,5 +280,4 @@
return defaultValue;
}
-
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
index 33bf7bc..ff88209 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
@@ -24,8 +24,8 @@
import com.android.ide.common.rendering.api.LayoutLog;
import com.android.ide.common.rendering.api.RenderSession;
import com.android.ide.common.rendering.api.Result;
-import com.android.ide.common.rendering.api.SessionParams;
import com.android.ide.common.rendering.api.Result.Status;
+import com.android.ide.common.rendering.api.SessionParams;
import com.android.layoutlib.bridge.impl.FontLoader;
import com.android.layoutlib.bridge.impl.RenderDrawable;
import com.android.layoutlib.bridge.impl.RenderSessionImpl;
@@ -242,6 +242,8 @@
if (fontLoader != null) {
Typeface_Delegate.init(fontLoader);
} else {
+ log.error(LayoutLog.TAG_BROKEN,
+ "Failed create FontLoader in layout lib.", null);
return false;
}
diff --git a/tools/obbtool/Android.mk b/tools/obbtool/Android.mk
index 72a9858..dd57ae6 100644
--- a/tools/obbtool/Android.mk
+++ b/tools/obbtool/Android.mk
@@ -19,6 +19,7 @@
LOCAL_STATIC_LIBRARIES := \
libutils \
+ libandroidfw \
libcutils
ifeq ($(HOST_OS),linux)
diff --git a/tools/validatekeymaps/Android.mk b/tools/validatekeymaps/Android.mk
index 1368a07..fce2e93 100644
--- a/tools/validatekeymaps/Android.mk
+++ b/tools/validatekeymaps/Android.mk
@@ -18,7 +18,7 @@
#LOCAL_C_INCLUDES +=
LOCAL_STATIC_LIBRARIES := \
- libui \
+ libandroidfw \
libutils \
libcutils