summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/current.txt2
-rw-r--r--cmds/stagefright/Android.mk24
-rw-r--r--cmds/stagefright/SimplePlayer.cpp646
-rw-r--r--cmds/stagefright/SimplePlayer.h109
-rw-r--r--cmds/stagefright/codec.cpp409
-rw-r--r--cmds/stagefright/sf2.cpp4
-rw-r--r--core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl3
-rw-r--r--core/java/android/accessibilityservice/UiTestAutomationBridge.java12
-rw-r--r--core/java/android/view/ActionMode.java2
-rw-r--r--core/java/android/view/View.java5
-rw-r--r--core/java/android/view/ViewGroup.java9
-rw-r--r--core/java/android/view/ViewRootImpl.java341
-rw-r--r--core/java/android/view/accessibility/AccessibilityInteractionClient.java8
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeInfo.java52
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeInfoCache.java (renamed from core/java/android/view/AccessibilityNodeInfoCache.java)98
-rw-r--r--core/java/android/view/accessibility/AccessibilityRecord.java7
-rw-r--r--core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl2
-rw-r--r--core/java/android/webkit/BrowserFrame.java115
-rw-r--r--core/java/android/webkit/CacheLoader.java79
-rw-r--r--core/java/android/webkit/CacheManager.java636
-rw-r--r--core/java/android/webkit/CallbackProxy.java8
-rw-r--r--core/java/android/webkit/ContentLoader.java98
-rw-r--r--core/java/android/webkit/CookieManager.java793
-rw-r--r--core/java/android/webkit/CookieSyncManager.java101
-rw-r--r--core/java/android/webkit/DataLoader.java77
-rw-r--r--core/java/android/webkit/FileLoader.java189
-rw-r--r--core/java/android/webkit/FrameLoader.java421
-rw-r--r--core/java/android/webkit/HttpAuthHandlerImpl.java279
-rw-r--r--core/java/android/webkit/JniUtil.java25
-rw-r--r--core/java/android/webkit/LoadListener.java1685
-rw-r--r--core/java/android/webkit/Network.java394
-rw-r--r--core/java/android/webkit/SslErrorHandlerImpl.java273
-rw-r--r--core/java/android/webkit/StreamLoader.java204
-rw-r--r--core/java/android/webkit/WebResourceResponse.java22
-rw-r--r--core/java/android/webkit/WebView.java235
-rw-r--r--core/java/android/webkit/WebViewCore.java52
-rw-r--r--core/java/android/webkit/WebViewDatabase.java648
-rw-r--r--core/java/android/webkit/WebViewWorker.java233
-rw-r--r--core/java/android/widget/AbsListView.java31
-rw-r--r--core/java/android/widget/TableLayout.java3
-rw-r--r--core/java/android/widget/TextView.java4652
-rw-r--r--core/java/android/widget/TimePicker.java2
-rw-r--r--core/java/com/android/internal/widget/ActionBarContextView.java6
-rw-r--r--core/java/com/google/android/mms/ContentType.java6
-rw-r--r--core/java/com/google/android/mms/pdu/PduComposer.java4
-rw-r--r--core/java/com/google/android/mms/pdu/PduPersister.java2
-rw-r--r--core/res/res/values-am/strings.xml2
-rw-r--r--core/res/res/values-es-rUS/strings.xml4
-rw-r--r--core/res/res/values-sl/strings.xml2
-rw-r--r--core/res/res/values-sw/strings.xml8
-rw-r--r--core/res/res/values-zu/strings.xml2
-rwxr-xr-xcore/res/res/values/attrs.xml31
-rw-r--r--core/res/res/values/public.xml16
-rw-r--r--core/tests/bandwidthtests/src/com/android/bandwidthtest/util/ConnectionUtil.java7
-rw-r--r--docs/html/images/training/lint_icon.pngbin0 -> 22128 bytes
-rw-r--r--docs/html/sdk/ndk/overview.jd2
-rw-r--r--docs/html/training/improving-layouts/optimizing-layout.jd45
-rw-r--r--graphics/java/android/graphics/Rect.java43
-rw-r--r--graphics/java/android/renderscript/Allocation.java2
-rw-r--r--include/media/stagefright/ACodec.h43
-rw-r--r--include/media/stagefright/MediaCodec.h183
-rw-r--r--include/media/stagefright/MediaErrors.h1
-rw-r--r--include/media/stagefright/NativeWindowWrapper.h26
-rw-r--r--include/media/stagefright/NuMediaExtractor.h80
-rw-r--r--include/media/stagefright/foundation/AMessage.h20
-rw-r--r--include/utils/KeyedVector.h6
-rw-r--r--libs/hwui/Debug.h2
-rw-r--r--libs/hwui/DisplayListRenderer.h1
-rw-r--r--libs/hwui/PathCache.cpp5
-rw-r--r--libs/rs/Allocation.cpp469
-rw-r--r--libs/rs/Allocation.h126
-rw-r--r--libs/rs/Android.mk9
-rw-r--r--libs/rs/BaseObj.cpp66
-rw-r--r--libs/rs/BaseObj.h52
-rw-r--r--libs/rs/Element.cpp426
-rw-r--r--libs/rs/Element.h199
-rw-r--r--libs/rs/RenderScript.cpp162
-rw-r--r--libs/rs/RenderScript.h156
-rw-r--r--libs/rs/Script.cpp67
-rw-r--r--libs/rs/Script.h84
-rw-r--r--libs/rs/ScriptC.cpp30
-rw-r--r--libs/rs/ScriptC.h35
-rw-r--r--libs/rs/Type.cpp159
-rw-r--r--libs/rs/Type.h99
-rw-r--r--libs/rs/driver/rsdBcc.cpp29
-rw-r--r--libs/rs/driver/rsdCore.h1
-rw-r--r--libs/rs/rsScript.cpp4
-rw-r--r--libs/rs/rsScript.h3
-rw-r--r--libs/rs/rsScriptC.cpp5
-rw-r--r--libs/rs/rsScriptC.h3
-rw-r--r--libs/rs/rsScriptC_Lib.cpp4
-rw-r--r--libs/rs/tests/Android.mk29
-rw-r--r--libs/rs/tests/compute.cpp37
-rw-r--r--libs/ui/Android.mk4
-rw-r--r--libs/ui/FramebufferNativeWindow.cpp12
-rw-r--r--media/java/android/media/MediaCodec.java210
-rw-r--r--media/java/android/media/MediaExtractor.java78
-rw-r--r--media/jni/Android.mk5
-rw-r--r--media/jni/android_media_MediaCodec.cpp550
-rw-r--r--media/jni/android_media_MediaCodec.h81
-rw-r--r--media/jni/android_media_MediaExtractor.cpp400
-rw-r--r--media/jni/android_media_MediaExtractor.h60
-rw-r--r--media/jni/android_media_MediaPlayer.cpp12
-rw-r--r--media/jni/android_media_Utils.cpp262
-rw-r--r--media/jni/android_media_Utils.h8
-rw-r--r--media/libmediaplayerservice/nuplayer/NuPlayer.cpp14
-rw-r--r--media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp9
-rw-r--r--media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp7
-rw-r--r--media/libmediaplayerservice/nuplayer/RTSPSource.cpp6
-rw-r--r--media/libstagefright/ACodec.cpp1128
-rw-r--r--media/libstagefright/Android.mk73
-rw-r--r--media/libstagefright/MPEG2TSWriter.cpp12
-rw-r--r--media/libstagefright/MediaCodec.cpp1180
-rw-r--r--media/libstagefright/NuMediaExtractor.cpp433
-rw-r--r--media/libstagefright/OMXClient.cpp4
-rwxr-xr-xmedia/libstagefright/OMXCodec.cpp3
-rw-r--r--media/libstagefright/foundation/AMessage.cpp39
-rw-r--r--media/libstagefright/rtsp/AAMRAssembler.cpp2
-rw-r--r--media/libstagefright/rtsp/AAVCAssembler.cpp2
-rw-r--r--media/libstagefright/rtsp/AH263Assembler.cpp2
-rw-r--r--media/libstagefright/rtsp/AMPEG4AudioAssembler.cpp2
-rw-r--r--media/libstagefright/rtsp/AMPEG4ElementaryAssembler.cpp2
-rw-r--r--media/libstagefright/rtsp/ARTPConnection.cpp8
-rw-r--r--media/libstagefright/rtsp/ARTPSession.cpp6
-rw-r--r--media/libstagefright/rtsp/ARTSPConnection.cpp2
-rw-r--r--media/libstagefright/rtsp/ARawAudioAssembler.cpp2
-rw-r--r--media/libstagefright/rtsp/MyHandler.h13
-rw-r--r--media/libstagefright/timedtext/Android.mk5
-rw-r--r--media/libstagefright/timedtext/TimedText3GPPSource.cpp (renamed from media/libstagefright/timedtext/TimedTextInBandSource.cpp)67
-rw-r--r--media/libstagefright/timedtext/TimedText3GPPSource.h (renamed from media/libstagefright/timedtext/TimedTextInBandSource.h)14
-rw-r--r--media/libstagefright/timedtext/TimedTextSource.cpp13
-rwxr-xr-xpolicy/src/com/android/internal/policy/impl/PhoneWindowManager.java10
-rw-r--r--services/java/com/android/server/accessibility/AccessibilityManagerService.java5
-rw-r--r--services/java/com/android/server/wm/DimAnimator.java4
-rw-r--r--services/java/com/android/server/wm/WindowManagerService.java5
-rw-r--r--tests/HwAccelerationTest/src/com/android/test/hwui/PathsCacheActivity.java31
-rw-r--r--tests/RenderScriptTests/SceneGraph/res/raw/unit_obj.a3dbin0 -> 29068 bytes
-rw-r--r--tests/RenderScriptTests/SceneGraph/src/com/android/testapp/SimpleAppRS.java96
-rw-r--r--tests/RenderScriptTests/tests/src/com/android/rs/test/RSTestCore.java12
-rw-r--r--tests/RenderScriptTests/tests/src/com/android/rs/test/UT_foreach.java5
-rw-r--r--tests/RenderScriptTests/tests/src/com/android/rs/test/UT_noroot.java57
-rw-r--r--tests/RenderScriptTests/tests/src/com/android/rs/test/foreach.rs42
-rw-r--r--tests/RenderScriptTests/tests/src/com/android/rs/test/noroot.rs44
-rw-r--r--tools/aapt/XMLNode.cpp12
-rw-r--r--tools/layoutlib/bridge/src/android/util/BridgeXmlPullAttributes.java191
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java4
146 files changed, 11781 insertions, 9258 deletions
diff --git a/api/current.txt b/api/current.txt
index d034b89c9d5c..0a83fe00b0fc 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -14884,7 +14884,7 @@ package android.os {
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/stagefright/Android.mk b/cmds/stagefright/Android.mk
index 11e94e83796b..f26747b6ead4 100644
--- a/cmds/stagefright/Android.mk
+++ b/cmds/stagefright/Android.mk
@@ -147,4 +147,28 @@ LOCAL_MODULE:= sf2
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 000000000000..f269e8099a14
--- /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 000000000000..2548252304c2
--- /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 000000000000..ad246d265e4b
--- /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 ae80f8804992..18e2532b463e 100644
--- a/cmds/stagefright/sf2.cpp
+++ b/cmds/stagefright/sf2.cpp
@@ -198,9 +198,7 @@ protected:
(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 882dd6bd5503..8d17325fe065 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -42,11 +42,12 @@ interface IAccessibilityServiceConnection {
* @param interactionId The id of the interaction for matching with the callback result.
* @param callback Callback which to receive the result.
* @param threadId The id of the calling thread.
+ * @param prefetchFlags flags to guide prefetching.
* @return The current window scale, where zero means a failure.
*/
float findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId,
long accessibilityNodeId, int interactionId,
- IAccessibilityInteractionConnectionCallback callback, long threadId);
+ IAccessibilityInteractionConnectionCallback callback, long threadId, int prefetchFlags);
/**
* Finds {@link android.view.accessibility.AccessibilityNodeInfo}s by View text.
diff --git a/core/java/android/accessibilityservice/UiTestAutomationBridge.java b/core/java/android/accessibilityservice/UiTestAutomationBridge.java
index 334981a10d19..9b3da53a6701 100644
--- a/core/java/android/accessibilityservice/UiTestAutomationBridge.java
+++ b/core/java/android/accessibilityservice/UiTestAutomationBridge.java
@@ -57,6 +57,11 @@ public class UiTestAutomationBridge {
public static final int UNDEFINED = -1;
+ private static final int FIND_ACCESSIBILITY_NODE_INFO_PREFETCH_FLAGS =
+ AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS
+ | AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS
+ | AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS;
+
private final Object mLock = new Object();
private volatile int mConnectionId = AccessibilityInteractionClient.NO_ID;
@@ -351,13 +356,15 @@ public class UiTestAutomationBridge {
ensureValidConnection(connectionId);
return AccessibilityInteractionClient.getInstance()
.findAccessibilityNodeInfoByAccessibilityId(mConnectionId,
- accessibilityWindowId, accessibilityNodeId);
+ accessibilityWindowId, accessibilityNodeId,
+ FIND_ACCESSIBILITY_NODE_INFO_PREFETCH_FLAGS);
}
/**
* Finds an {@link AccessibilityNodeInfo} by View id in the active
* window. The search is performed from the root node.
*
+ * @param viewId The id of a View.
* @return The current window scale, where zero means a failure.
*/
public AccessibilityNodeInfo findAccessibilityNodeInfoByViewIdInActiveWindow(int viewId) {
@@ -373,6 +380,7 @@ public class UiTestAutomationBridge {
* {@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 viewId The id of a View.
* @return The current window scale, where zero means a failure.
*/
public AccessibilityNodeInfo findAccessibilityNodeInfoByViewId(int accessibilityWindowId,
@@ -460,7 +468,7 @@ public class UiTestAutomationBridge {
ensureValidConnection(connectionId);
return AccessibilityInteractionClient.getInstance()
.findAccessibilityNodeInfoByAccessibilityId(connectionId, ACTIVE_WINDOW_ID,
- ROOT_NODE_ID);
+ ROOT_NODE_ID, AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS);
}
private void ensureValidConnection(int connectionId) {
diff --git a/core/java/android/view/ActionMode.java b/core/java/android/view/ActionMode.java
index 0349a2be3fbf..c1c7fe26776e 100644
--- a/core/java/android/view/ActionMode.java
+++ b/core/java/android/view/ActionMode.java
@@ -24,7 +24,7 @@ package android.view;
* <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 384e39a4d2e2..0675a7493e94 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -13055,11 +13055,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.REQUEST_LAYOUT);
}
- if (getAccessibilityNodeProvider() != null) {
- throw new IllegalStateException("Views with AccessibilityNodeProvider"
- + " can't have children.");
- }
-
mPrivateFlags |= FORCE_LAYOUT;
mPrivateFlags |= INVALIDATED;
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index c68d77d6d3ac..2848e8802927 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -2669,6 +2669,15 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
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 28737fcaa091..31d874f6a75c 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -54,12 +54,12 @@ import android.util.AndroidRuntimeException;
import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Log;
-import android.util.LongSparseArray;
import android.util.Pool;
import android.util.Poolable;
import android.util.PoolableManager;
import android.util.Pools;
import android.util.Slog;
+import android.util.SparseLongArray;
import android.util.TypedValue;
import android.view.View.AttachInfo;
import android.view.View.MeasureSpec;
@@ -87,7 +87,9 @@ import java.io.IOException;
import java.io.OutputStream;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/**
* The top of a view hierarchy, implementing the needed protocol between View
@@ -309,7 +311,7 @@ public final class ViewRootImpl implements ViewParent,
SendWindowContentChangedAccessibilityEvent mSendWindowContentChangedAccessibilityEvent;
- AccessibilityPrefetchStrategy mAccessibilityPrefetchStrategy;
+ AccessibilityNodePrefetcher mAccessibilityNodePrefetcher;
private final int mDensity;
@@ -379,8 +381,6 @@ public final class ViewRootImpl implements ViewParent,
mAccessibilityManager = AccessibilityManager.getInstance(context);
mAccessibilityInteractionConnectionManager =
new AccessibilityInteractionConnectionManager();
- mAccessibilityManager.addAccessibilityStateChangeListener(
- mAccessibilityInteractionConnectionManager);
mAttachInfo = new View.AttachInfo(sWindowSession, mWindow, this, mHandler, this);
mViewConfiguration = ViewConfiguration.get(context);
mDensity = context.getResources().getDisplayMetrics().densityDpi;
@@ -771,19 +771,29 @@ public final class ViewRootImpl implements ViewParent,
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 @@ public final class ViewRootImpl implements ViewParent,
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 final class ViewRootImpl implements ViewParent,
}
}
}
-
- public ViewParent getParent() {
- return null;
- }
- public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
- invalidateChild(null, dirty);
+ public ViewParent getParent() {
return null;
}
@@ -3554,15 +3564,15 @@ public final class ViewRootImpl implements ViewParent,
return mAccessibilityInteractionController;
}
- public AccessibilityPrefetchStrategy getAccessibilityPrefetchStrategy() {
+ public AccessibilityNodePrefetcher getAccessibilityNodePrefetcher() {
if (mView == null) {
- throw new IllegalStateException("getAccessibilityPrefetchStrategy"
+ throw new IllegalStateException("getAccessibilityNodePrefetcher"
+ " called when there is no mView");
}
- if (mAccessibilityPrefetchStrategy == null) {
- mAccessibilityPrefetchStrategy = new AccessibilityPrefetchStrategy();
+ if (mAccessibilityNodePrefetcher == null) {
+ mAccessibilityNodePrefetcher = new AccessibilityNodePrefetcher();
}
- return mAccessibilityPrefetchStrategy;
+ return mAccessibilityNodePrefetcher;
}
private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
@@ -4108,7 +4118,6 @@ public final class ViewRootImpl implements ViewParent,
if (mView == null) {
return false;
}
- getAccessibilityPrefetchStrategy().onAccessibilityEvent(event);
mAccessibilityManager.sendAccessibilityEvent(event);
return true;
}
@@ -4680,12 +4689,12 @@ public final class ViewRootImpl implements ViewParent,
public void findAccessibilityNodeInfoByAccessibilityId(long accessibilityNodeId,
int interactionId, IAccessibilityInteractionConnectionCallback callback,
- int interrogatingPid, long interrogatingTid) {
+ int prefetchFlags, int interrogatingPid, long interrogatingTid) {
ViewRootImpl viewRootImpl = mViewRootImpl.get();
if (viewRootImpl != null && viewRootImpl.mView != null) {
viewRootImpl.getAccessibilityInteractionController()
.findAccessibilityNodeInfoByAccessibilityIdClientThread(accessibilityNodeId,
- interactionId, callback, interrogatingPid, interrogatingTid);
+ interactionId, callback, prefetchFlags, interrogatingPid, interrogatingTid);
} else {
// We cannot make the call and notify the caller so it does not wait.
try {
@@ -4820,11 +4829,11 @@ public final class ViewRootImpl implements ViewParent,
public void findAccessibilityNodeInfoByAccessibilityIdClientThread(
long accessibilityNodeId, int interactionId,
- IAccessibilityInteractionConnectionCallback callback, int interrogatingPid,
- long interrogatingTid) {
+ IAccessibilityInteractionConnectionCallback callback, int prefetchFlags,
+ int interrogatingPid, long interrogatingTid) {
Message message = mHandler.obtainMessage();
message.what = MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID;
- message.arg1 = interrogatingPid;
+ message.arg1 = prefetchFlags;
SomeArgs args = mPool.acquire();
args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
@@ -4845,8 +4854,8 @@ public final class ViewRootImpl implements ViewParent,
}
public void findAccessibilityNodeInfoByAccessibilityIdUiThread(Message message) {
+ final int prefetchFlags = message.arg1;
SomeArgs args = (SomeArgs) message.obj;
- final int interrogatingPid = message.arg1;
final int accessibilityViewId = args.argi1;
final int virtualDescendantId = args.argi2;
final int interactionId = args.argi3;
@@ -4856,22 +4865,15 @@ public final class ViewRootImpl implements ViewParent,
List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList;
infos.clear();
try {
+ View target = null;
if (accessibilityViewId == AccessibilityNodeInfo.UNDEFINED) {
- View target = ViewRootImpl.this.mView;
- if (target != null && target.getVisibility() == View.VISIBLE) {
- infos.add(target.createAccessibilityNodeInfo());
- }
+ target = ViewRootImpl.this.mView;
} 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);
- }
- }
+ target = findViewByAccessibilityId(accessibilityViewId);
+ }
+ if (target != null && target.getVisibility() == View.VISIBLE) {
+ getAccessibilityNodePrefetcher().prefetchAccessibilityNodeInfos(target,
+ virtualDescendantId, prefetchFlags, infos);
}
} finally {
try {
@@ -5132,83 +5134,216 @@ public final class ViewRootImpl implements ViewParent,
/**
* This class encapsulates a prefetching strategy for the accessibility APIs for
- * querying window content.It is responsible to prefetch a batch of
- * AccessibilityNodeInfos in addition to the one for a requested node. It caches
- * the ids of the prefeteched nodes such that they are fetched only once.
+ * querying window content. It is responsible to prefetch a batch of
+ * AccessibilityNodeInfos in addition to the one for a requested node.
*/
- class AccessibilityPrefetchStrategy {
- private static final int MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE = 100;
+ class AccessibilityNodePrefetcher {
- // We need to keep track of what we have sent for each interrogating
- // process. Usually there will be only one such process but we
- // should support the general case. Note that the accessibility event
- // stream will take care of clearing caches of querying processes that
- // are not longer alive, so we do not waste memory.
- private final LongSparseArray<AccessibilityNodeInfoCache> mAccessibilityNodeInfoCaches =
- new LongSparseArray<AccessibilityNodeInfoCache>();
+ private static final int MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE = 50;
- private AccessibilityNodeInfoCache getCacheForInterrogatingPid(long interrogatingPid) {
- AccessibilityNodeInfoCache cache = mAccessibilityNodeInfoCaches.get(interrogatingPid);
- if (cache == null) {
- cache = AccessibilityNodeInfoCache.newAccessibilityNodeInfoCache();
- mAccessibilityNodeInfoCaches.put(interrogatingPid, cache);
+ public void prefetchAccessibilityNodeInfos(View view, int virtualViewId, int prefetchFlags,
+ List<AccessibilityNodeInfo> outInfos) {
+ AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
+ if (provider == null) {
+ AccessibilityNodeInfo root = view.createAccessibilityNodeInfo();
+ if (root != null) {
+ outInfos.add(root);
+ if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
+ prefetchPredecessorsOfRealNode(view, outInfos);
+ }
+ if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
+ prefetchSiblingsOfRealNode(view, outInfos);
+ }
+ if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
+ prefetchDescendantsOfRealNode(view, outInfos);
+ }
+ }
+ } else {
+ AccessibilityNodeInfo root = provider.createAccessibilityNodeInfo(virtualViewId);
+ if (root != null) {
+ outInfos.add(root);
+ if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
+ prefetchPredecessorsOfVirtualNode(root, view, provider, outInfos);
+ }
+ if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
+ prefetchSiblingsOfVirtualNode(root, view, provider, outInfos);
+ }
+ if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
+ prefetchDescendantsOfVirtualNode(root, provider, outInfos);
+ }
+ }
}
- return cache;
}
- public void onAccessibilityEvent(AccessibilityEvent event) {
- final int cacheCount = mAccessibilityNodeInfoCaches.size();
- for (int i = 0; i < cacheCount; i++) {
- AccessibilityNodeInfoCache cache = mAccessibilityNodeInfoCaches.valueAt(i);
- cache.onAccessibilityEvent(event);
+ private void prefetchPredecessorsOfRealNode(View view,
+ List<AccessibilityNodeInfo> outInfos) {
+ ViewParent parent = view.getParent();
+ while (parent instanceof View
+ && outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
+ View parentView = (View) parent;
+ final long parentNodeId = AccessibilityNodeInfo.makeNodeId(
+ parentView.getAccessibilityViewId(), AccessibilityNodeInfo.UNDEFINED);
+ AccessibilityNodeInfo info = parentView.createAccessibilityNodeInfo();
+ if (info != null) {
+ outInfos.add(info);
+ }
+ parent = parent.getParent();
}
}
- public void prefetchAccessibilityNodeInfos(long interrogatingPid, View root,
+ private void prefetchSiblingsOfRealNode(View current,
List<AccessibilityNodeInfo> outInfos) {
- addAndCacheNotCachedNodeInfo(interrogatingPid, root, outInfos);
- addAndCacheNotCachedPredecessorInfos(interrogatingPid, root, outInfos);
- addAndCacheNotCachedDescendantInfos(interrogatingPid, root, outInfos);
+ ViewParent parent = current.getParent();
+ if (parent instanceof ViewGroup) {
+ ViewGroup parentGroup = (ViewGroup) parent;
+ final int childCount = parentGroup.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = parentGroup.getChildAt(i);
+ if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE
+ && child.getAccessibilityViewId() != current.getAccessibilityViewId()
+ && child.getVisibility() == View.VISIBLE) {
+ final long childNodeId = AccessibilityNodeInfo.makeNodeId(
+ child.getAccessibilityViewId(), AccessibilityNodeInfo.UNDEFINED);
+ AccessibilityNodeInfo info = null;
+ AccessibilityNodeProvider provider = child.getAccessibilityNodeProvider();
+ if (provider == null) {
+ info = child.createAccessibilityNodeInfo();
+ } else {
+ info = provider.createAccessibilityNodeInfo(
+ AccessibilityNodeInfo.UNDEFINED);
+ }
+ if (info != null) {
+ outInfos.add(info);
+ }
+ }
+ }
+ }
}
- private void addAndCacheNotCachedNodeInfo(long interrogatingPid,
- View view, List<AccessibilityNodeInfo> outInfos) {
- final long accessibilityNodeId = AccessibilityNodeInfo.makeNodeId(
- view.getAccessibilityViewId(), AccessibilityNodeInfo.UNDEFINED);
- AccessibilityNodeInfoCache cache = getCacheForInterrogatingPid(interrogatingPid);
- if (!cache.containsKey(accessibilityNodeId)) {
- // Account for the ids of the fetched infos. The infos will be
- // cached in the window querying process. We just need to know
- // which infos are cached to avoid fetching a cached one again.
- cache.put(accessibilityNodeId, null);
- outInfos.add(view.createAccessibilityNodeInfo());
+ private void prefetchDescendantsOfRealNode(View root,
+ List<AccessibilityNodeInfo> outInfos) {
+ if (root instanceof ViewGroup) {
+ ViewGroup rootGroup = (ViewGroup) root;
+ HashMap<View, AccessibilityNodeInfo> addedChildren =
+ new HashMap<View, AccessibilityNodeInfo>();
+ final int childCount = rootGroup.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = rootGroup.getChildAt(i);
+ if (child.getVisibility() == View.VISIBLE
+ && outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
+ final long childNodeId = AccessibilityNodeInfo.makeNodeId(
+ child.getAccessibilityViewId(), AccessibilityNodeInfo.UNDEFINED);
+ AccessibilityNodeProvider provider = child.getAccessibilityNodeProvider();
+ if (provider == null) {
+ AccessibilityNodeInfo info = child.createAccessibilityNodeInfo();
+ if (info != null) {
+ outInfos.add(info);
+ addedChildren.put(child, null);
+ }
+ } else {
+ AccessibilityNodeInfo info = provider.createAccessibilityNodeInfo(
+ AccessibilityNodeInfo.UNDEFINED);
+ if (info != null) {
+ outInfos.add(info);
+ addedChildren.put(child, info);
+ }
+ }
+ }
+ }
+ if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
+ for (Map.Entry<View, AccessibilityNodeInfo> entry : addedChildren.entrySet()) {
+ View addedChild = entry.getKey();
+ AccessibilityNodeInfo virtualRoot = entry.getValue();
+ if (virtualRoot == null) {
+ prefetchDescendantsOfRealNode(addedChild, outInfos);
+ } else {
+ AccessibilityNodeProvider provider =
+ addedChild.getAccessibilityNodeProvider();
+ prefetchDescendantsOfVirtualNode(virtualRoot, provider, outInfos);
+ }
+ }
+ }
}
}
- private void addAndCacheNotCachedPredecessorInfos(long interrogatingPid, View view,
+ private void prefetchPredecessorsOfVirtualNode(AccessibilityNodeInfo root,
+ View providerHost, AccessibilityNodeProvider provider,
List<AccessibilityNodeInfo> outInfos) {
- ViewParent predecessor = view.getParent();
- while (predecessor instanceof View
- && outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
- View predecessorView = (View) predecessor;
- addAndCacheNotCachedNodeInfo(interrogatingPid, predecessorView, outInfos);
- predecessor = predecessor.getParent();
+ long parentNodeId = root.getParentNodeId();
+ int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
+ while (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
+ final int virtualDescendantId =
+ AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId);
+ if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED
+ || accessibilityViewId == providerHost.getAccessibilityViewId()) {
+ AccessibilityNodeInfo parent = provider.createAccessibilityNodeInfo(
+ virtualDescendantId);
+ if (parent != null) {
+ outInfos.add(parent);
+ }
+ parentNodeId = parent.getParentNodeId();
+ accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(
+ parentNodeId);
+ } else {
+ prefetchPredecessorsOfRealNode(providerHost, outInfos);
+ return;
+ }
}
}
- private void addAndCacheNotCachedDescendantInfos(long interrogatingPid, View view,
- List<AccessibilityNodeInfo> outInfos) {
- if (outInfos.size() > MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE
- || view.getAccessibilityNodeProvider() != null) {
- return;
+ private void prefetchSiblingsOfVirtualNode(AccessibilityNodeInfo current, View providerHost,
+ AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) {
+ final long parentNodeId = current.getParentNodeId();
+ final int parentAccessibilityViewId =
+ AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
+ final int parentVirtualDescendantId =
+ AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId);
+ if (parentVirtualDescendantId != AccessibilityNodeInfo.UNDEFINED
+ || parentAccessibilityViewId == providerHost.getAccessibilityViewId()) {
+ AccessibilityNodeInfo parent =
+ provider.createAccessibilityNodeInfo(parentVirtualDescendantId);
+ if (parent != null) {
+ SparseLongArray childNodeIds = parent.getChildNodeIds();
+ final int childCount = childNodeIds.size();
+ for (int i = 0; i < childCount; i++) {
+ final long childNodeId = childNodeIds.get(i);
+ if (childNodeId != current.getSourceNodeId()
+ && outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
+ final int childVirtualDescendantId =
+ AccessibilityNodeInfo.getVirtualDescendantId(childNodeId);
+ AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo(
+ childVirtualDescendantId);
+ if (child != null) {
+ outInfos.add(child);
+ }
+ }
+ }
+ }
+ } else {
+ prefetchSiblingsOfRealNode(providerHost, outInfos);
+ }
+ }
+
+ private void prefetchDescendantsOfVirtualNode(AccessibilityNodeInfo root,
+ AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) {
+ SparseLongArray childNodeIds = root.getChildNodeIds();
+ final int initialOutInfosSize = outInfos.size();
+ final int childCount = childNodeIds.size();
+ for (int i = 0; i < childCount; i++) {
+ if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
+ final long childNodeId = childNodeIds.get(i);
+ AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo(
+ AccessibilityNodeInfo.getVirtualDescendantId(childNodeId));
+ if (child != null) {
+ outInfos.add(child);
+ }
+ }
}
- addAndCacheNotCachedNodeInfo(interrogatingPid, view, outInfos);
- if (view instanceof ViewGroup) {
- ViewGroup rootGroup = (ViewGroup) view;
- final int childCount = rootGroup.getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = rootGroup.getChildAt(i);
- addAndCacheNotCachedDescendantInfos(interrogatingPid, child, outInfos);
+ if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
+ final int addedChildCount = outInfos.size() - initialOutInfosSize;
+ for (int i = 0; i < addedChildCount; i++) {
+ AccessibilityNodeInfo child = outInfos.get(initialOutInfosSize + i);
+ prefetchDescendantsOfVirtualNode(child, provider, outInfos);
}
}
}
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index 105c010d4be9..be74b312c1ea 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -24,7 +24,6 @@ import android.os.SystemClock;
import android.util.Log;
import android.util.LongSparseArray;
import android.util.SparseArray;
-import android.view.AccessibilityNodeInfoCache;
import java.util.ArrayList;
import java.util.Collections;
@@ -102,7 +101,7 @@ public final class AccessibilityInteractionClient
// The connection cache is shared between all interrogating threads since
// at any given time there is only one window allowing querying.
private static final AccessibilityNodeInfoCache sAccessibilityNodeInfoCache =
- AccessibilityNodeInfoCache.newSynchronizedAccessibilityNodeInfoCache();
+ new AccessibilityNodeInfoCache();
/**
* @return The client for the current thread.
@@ -159,10 +158,11 @@ public final class AccessibilityInteractionClient
* where to start the search. Use
* {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
* to start from the root.
+ * @param prefetchFlags flags to guide prefetching.
* @return An {@link AccessibilityNodeInfo} if found, null otherwise.
*/
public AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId(int connectionId,
- int accessibilityWindowId, long accessibilityNodeId) {
+ int accessibilityWindowId, long accessibilityNodeId, int prefetchFlags) {
try {
IAccessibilityServiceConnection connection = getConnection(connectionId);
if (connection != null) {
@@ -174,7 +174,7 @@ public final class AccessibilityInteractionClient
final int interactionId = mInteractionIdCounter.getAndIncrement();
final float windowScale = connection.findAccessibilityNodeInfoByAccessibilityId(
accessibilityWindowId, accessibilityNodeId, interactionId, this,
- Thread.currentThread().getId());
+ Thread.currentThread().getId(), prefetchFlags);
// If the scale is zero the call has failed.
if (windowScale > 0) {
List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 84ad268f59e1..c094fda58a46 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -61,6 +61,15 @@ public class AccessibilityNodeInfo implements Parcelable {
/** @hide */
public static final int ACTIVE_WINDOW_ID = UNDEFINED;
+ /** @hide */
+ public static final int FLAG_PREFETCH_PREDECESSORS = 0x00000001;
+
+ /** @hide */
+ public static final int FLAG_PREFETCH_SIBLINGS = 0x00000002;
+
+ /** @hide */
+ public static final int FLAG_PREFETCH_DESCENDANTS = 0x00000003;
+
// Actions.
/**
@@ -181,7 +190,7 @@ public class AccessibilityNodeInfo implements Parcelable {
private CharSequence mText;
private CharSequence mContentDescription;
- private SparseLongArray mChildIds = new SparseLongArray();
+ private SparseLongArray mChildNodeIds = new SparseLongArray();
private int mActions;
private int mConnectionId = UNDEFINED;
@@ -244,12 +253,21 @@ public class AccessibilityNodeInfo implements Parcelable {
}
/**
+ * @return The ids of the children.
+ *
+ * @hide
+ */
+ public SparseLongArray getChildNodeIds() {
+ return mChildNodeIds;
+ }
+
+ /**
* Gets the number of children.
*
* @return The child count.
*/
public int getChildCount() {
- return mChildIds.size();
+ return mChildNodeIds.size();
}
/**
@@ -271,9 +289,10 @@ public class AccessibilityNodeInfo implements Parcelable {
if (!canPerformRequestOverConnection(mSourceNodeId)) {
return null;
}
- final long childId = mChildIds.get(index);
+ final long childId = mChildNodeIds.get(index);
AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
- return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mWindowId, childId);
+ return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mWindowId,
+ childId, FLAG_PREFETCH_DESCENDANTS);
}
/**
@@ -308,11 +327,11 @@ public class AccessibilityNodeInfo implements Parcelable {
*/
public void addChild(View root, int virtualDescendantId) {
enforceNotSealed();
- final int index = mChildIds.size();
+ final int index = mChildNodeIds.size();
final int rootAccessibilityViewId =
(root != null) ? root.getAccessibilityViewId() : UNDEFINED;
final long childNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
- mChildIds.put(index, childNodeId);
+ mChildNodeIds.put(index, childNodeId);
}
/**
@@ -408,7 +427,16 @@ public class AccessibilityNodeInfo implements Parcelable {
}
AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId,
- mWindowId, mParentNodeId);
+ mWindowId, mParentNodeId, FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS);
+ }
+
+ /**
+ * @return The parent node id.
+ *
+ * @hide
+ */
+ public long getParentNodeId() {
+ return mParentNodeId;
}
/**
@@ -1070,7 +1098,7 @@ public class AccessibilityNodeInfo implements Parcelable {
parcel.writeLong(mParentNodeId);
parcel.writeInt(mConnectionId);
- SparseLongArray childIds = mChildIds;
+ SparseLongArray childIds = mChildNodeIds;
final int childIdsSize = childIds.size();
parcel.writeInt(childIdsSize);
for (int i = 0; i < childIdsSize; i++) {
@@ -1120,7 +1148,7 @@ public class AccessibilityNodeInfo implements Parcelable {
mContentDescription = other.mContentDescription;
mActions= other.mActions;
mBooleanProperties = other.mBooleanProperties;
- mChildIds = other.mChildIds.clone();
+ mChildNodeIds = other.mChildNodeIds.clone();
}
/**
@@ -1135,7 +1163,7 @@ public class AccessibilityNodeInfo implements Parcelable {
mParentNodeId = parcel.readLong();
mConnectionId = parcel.readInt();
- SparseLongArray childIds = mChildIds;
+ SparseLongArray childIds = mChildNodeIds;
final int childrenSize = parcel.readInt();
for (int i = 0; i < childrenSize; i++) {
final long childId = parcel.readLong();
@@ -1171,7 +1199,7 @@ public class AccessibilityNodeInfo implements Parcelable {
mParentNodeId = ROOT_NODE_ID;
mWindowId = UNDEFINED;
mConnectionId = UNDEFINED;
- mChildIds.clear();
+ mChildNodeIds.clear();
mBoundsInParent.set(0, 0, 0, 0);
mBoundsInScreen.set(0, 0, 0, 0);
mBooleanProperties = 0;
@@ -1249,7 +1277,7 @@ public class AccessibilityNodeInfo implements Parcelable {
builder.append("; accessibilityViewId: " + getAccessibilityViewId(mSourceNodeId));
builder.append("; virtualDescendantId: " + getVirtualDescendantId(mSourceNodeId));
builder.append("; mParentNodeId: " + mParentNodeId);
- SparseLongArray childIds = mChildIds;
+ SparseLongArray childIds = mChildNodeIds;
builder.append("; childAccessibilityIds: [");
for (int i = 0, count = childIds.size(); i < count; i++) {
builder.append(childIds.valueAt(i));
diff --git a/core/java/android/view/AccessibilityNodeInfoCache.java b/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java
index 84b510d8c7ef..4fb0046ffe12 100644
--- a/core/java/android/view/AccessibilityNodeInfoCache.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java
@@ -14,13 +14,11 @@
* limitations under the License.
*/
-package android.view;
+package android.view.accessibility;
import android.os.Process;
import android.util.Log;
import android.util.LongSparseArray;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
/**
* Simple cache for AccessibilityNodeInfos. The cache is mapping an
@@ -38,53 +36,11 @@ public class AccessibilityNodeInfoCache {
private static final boolean DEBUG = false;
- /**
- * @return A new <strong>not synchronized</strong> AccessibilityNodeInfoCache.
- */
- public static AccessibilityNodeInfoCache newAccessibilityNodeInfoCache() {
- return new AccessibilityNodeInfoCache();
- }
-
- /**
- * @return A new <strong>synchronized</strong> AccessibilityNodeInfoCache.
- */
- public static AccessibilityNodeInfoCache newSynchronizedAccessibilityNodeInfoCache() {
- return new AccessibilityNodeInfoCache() {
- private final Object mLock = new Object();
-
- @Override
- public void clear() {
- synchronized(mLock) {
- super.clear();
- }
- }
-
- @Override
- public AccessibilityNodeInfo get(long accessibilityNodeId) {
- synchronized(mLock) {
- return super.get(accessibilityNodeId);
- }
- }
-
- @Override
- public void put(long accessibilityNodeId, AccessibilityNodeInfo info) {
- synchronized(mLock) {
- super.put(accessibilityNodeId, info);
- }
- }
-
- @Override
- public void remove(long accessibilityNodeId) {
- synchronized(mLock) {
- super.remove(accessibilityNodeId);
- }
- }
- };
- }
+ private final Object mLock = new Object();
private final LongSparseArray<AccessibilityNodeInfo> mCacheImpl;
- private AccessibilityNodeInfoCache() {
+ public AccessibilityNodeInfoCache() {
if (ENABLED) {
mCacheImpl = new LongSparseArray<AccessibilityNodeInfo>();
} else {
@@ -124,13 +80,15 @@ public class AccessibilityNodeInfoCache {
*/
public AccessibilityNodeInfo get(long accessibilityNodeId) {
if (ENABLED) {
- if (DEBUG) {
- AccessibilityNodeInfo info = mCacheImpl.get(accessibilityNodeId);
- Log.i(LOG_TAG, "Process: " + Process.myPid() +
- " get(" + accessibilityNodeId + ") = " + info);
- return info;
- } else {
- return mCacheImpl.get(accessibilityNodeId);
+ synchronized(mLock) {
+ 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;
@@ -145,11 +103,13 @@ public class AccessibilityNodeInfoCache {
*/
public void put(long accessibilityNodeId, AccessibilityNodeInfo info) {
if (ENABLED) {
- if (DEBUG) {
- Log.i(LOG_TAG, "Process: " + Process.myPid()
- + " put(" + accessibilityNodeId + ", " + info + ")");
+ synchronized(mLock) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Process: " + Process.myPid()
+ + " put(" + accessibilityNodeId + ", " + info + ")");
+ }
+ mCacheImpl.put(accessibilityNodeId, info);
}
- mCacheImpl.put(accessibilityNodeId, info);
}
}
@@ -161,7 +121,9 @@ public class AccessibilityNodeInfoCache {
*/
public boolean containsKey(long accessibilityNodeId) {
if (ENABLED) {
- return (mCacheImpl.indexOfKey(accessibilityNodeId) >= 0);
+ synchronized(mLock) {
+ return (mCacheImpl.indexOfKey(accessibilityNodeId) >= 0);
+ }
} else {
return false;
}
@@ -174,11 +136,13 @@ public class AccessibilityNodeInfoCache {
*/
public void remove(long accessibilityNodeId) {
if (ENABLED) {
- if (DEBUG) {
- Log.i(LOG_TAG, "Process: " + Process.myPid()
- + " remove(" + accessibilityNodeId + ")");
+ synchronized(mLock) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Process: " + Process.myPid()
+ + " remove(" + accessibilityNodeId + ")");
+ }
+ mCacheImpl.remove(accessibilityNodeId);
}
- mCacheImpl.remove(accessibilityNodeId);
}
}
@@ -187,10 +151,12 @@ public class AccessibilityNodeInfoCache {
*/
public void clear() {
if (ENABLED) {
- if (DEBUG) {
- Log.i(LOG_TAG, "Process: " + Process.myPid() + "clear()");
+ synchronized(mLock) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Process: " + Process.myPid() + "clear()");
+ }
+ mCacheImpl.clear();
}
- mCacheImpl.clear();
}
}
}
diff --git a/core/java/android/view/accessibility/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java
index b60f50eb5d9d..23b235cbe395 100644
--- a/core/java/android/view/accessibility/AccessibilityRecord.java
+++ b/core/java/android/view/accessibility/AccessibilityRecord.java
@@ -56,6 +56,11 @@ public class AccessibilityRecord {
private static final int PROPERTY_FULL_SCREEN = 0x00000080;
private static final int PROPERTY_SCROLLABLE = 0x00000100;
+ private static final int GET_SOURCE_PREFETCH_FLAGS =
+ AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS
+ | AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS
+ | AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS;
+
// Housekeeping
private static final int MAX_POOL_SIZE = 10;
private static final Object sPoolLock = new Object();
@@ -144,7 +149,7 @@ public class AccessibilityRecord {
}
AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mSourceWindowId,
- mSourceNodeId);
+ mSourceNodeId, GET_SOURCE_PREFETCH_FLAGS);
}
/**
diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
index ae6869cad9f5..fc3651c6451f 100644
--- a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
@@ -28,7 +28,7 @@ import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
oneway interface IAccessibilityInteractionConnection {
void findAccessibilityNodeInfoByAccessibilityId(long accessibilityNodeId, int interactionId,
- IAccessibilityInteractionConnectionCallback callback,
+ IAccessibilityInteractionConnectionCallback callback, int prefetchFlags,
int interrogatingPid, long interrogatingTid);
void findAccessibilityNodeInfoByViewId(long accessibilityNodeId, int id, int interactionId,
diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java
index 3613ec964d8f..8ccc59c78a9c 100644
--- a/core/java/android/webkit/BrowserFrame.java
+++ b/core/java/android/webkit/BrowserFrame.java
@@ -471,18 +471,6 @@ class BrowserFrame extends Handler {
}
/**
- * 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 @@ class BrowserFrame extends Handler {
}
}
}
- if (!JniUtil.useChromiumHttpStack()) {
- WebViewWorker.getHandler().sendEmptyMessage(
- WebViewWorker.MSG_TRIM_CACHE);
- }
break;
}
@@ -767,10 +751,9 @@ class BrowserFrame extends Handler {
} 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 @@ class BrowserFrame extends Handler {
}
/**
- * 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).
diff --git a/core/java/android/webkit/CacheLoader.java b/core/java/android/webkit/CacheLoader.java
deleted file mode 100644
index 072104562f16..000000000000
--- 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 36b4de16d36d..6a85e0026331 100644
--- a/core/java/android/webkit/CacheManager.java
+++ b/core/java/android/webkit/CacheManager.java
@@ -57,33 +57,8 @@ public final class CacheManager {
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;
-
- // 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
@@ -260,51 +235,12 @@ public final class CacheManager {
* @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;
}
/**
@@ -329,51 +265,6 @@ public final class CacheManager {
return false;
}
- // 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
@@ -415,9 +306,8 @@ public final class CacheManager {
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;
@@ -435,49 +325,6 @@ public final class CacheManager {
// 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) {
- 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.
@@ -516,66 +363,8 @@ public final class CacheManager {
*/
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();
-
- 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;
}
/**
@@ -598,64 +387,24 @@ public final class CacheManager {
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;
}
/**
@@ -664,21 +413,6 @@ public final class CacheManager {
* @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() {
@@ -703,323 +437,5 @@ public final class CacheManager {
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 95d927593799..3a05bcab12e8 100644
--- a/core/java/android/webkit/CallbackProxy.java
+++ b/core/java/android/webkit/CallbackProxy.java
@@ -920,10 +920,6 @@ class CallbackProxy extends Handler {
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 @@ class CallbackProxy extends Handler {
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 d13210aa2a0b..000000000000
--- 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 c500a7621fb3..497cab7d2392 100644
--- a/core/java/android/webkit/CookieManager.java
+++ b/core/java/android/webkit/CookieManager.java
@@ -95,11 +95,6 @@ public final class CookieManager {
// 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 @@ public final class CookieManager {
* cookies
*/
public synchronized void setAcceptCookie(boolean accept) {
- if (JniUtil.useChromiumHttpStack()) {
- nativeSetAcceptCookie(accept);
- return;
- }
-
- mAcceptCookie = accept;
+ nativeSetAcceptCookie(accept);
}
/**
@@ -282,11 +272,7 @@ public final class CookieManager {
* @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 @@ public final class CookieManager {
* '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 @@ public final class CookieManager {
* @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 @@ public final class CookieManager {
}
/**
- * 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 @@ public final class CookieManager {
* @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 @@ public final class CookieManager {
* @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 final class CookieManager {
*/
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 @@ public final class CookieManager {
* @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 @@ public final class CookieManager {
* @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 @@ public final class CookieManager {
* 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 @@ public final class CookieManager {
* Flush all cookies managed by the Chrome HTTP stack to flash.
*/
void flushCookieStore() {
- if (JniUtil.useChromiumHttpStack()) {
- nativeFlushCookieStore();
- }
+ nativeFlushCookieStore();
}
/**
@@ -741,11 +436,7 @@ public final class CookieManager {
* file scheme URLs
*/
public static boolean allowFileSchemeCookies() {
- if (JniUtil.useChromiumHttpStack()) {
- return nativeAcceptFileSchemeCookies();
- } else {
- return true;
- }
+ return nativeAcceptFileSchemeCookies();
}
/**
@@ -759,457 +450,7 @@ public final class CookieManager {
* {@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 a6998006e895..19fa09672c42 100644
--- a/core/java/android/webkit/CookieSyncManager.java
+++ b/core/java/android/webkit/CookieSyncManager.java
@@ -100,77 +100,6 @@ public final class CookieSyncManager extends WebSyncManager {
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 @@ public final class CookieSyncManager extends WebSyncManager {
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 e8d906934954..000000000000
--- 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 e21e9ef8e9f1..000000000000
--- 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 0d8030285244..000000000000
--- 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 01e8eb8a644f..000000000000
--- 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 7b449385ebfa..343d34a56643 100644
--- a/core/java/android/webkit/JniUtil.java
+++ b/core/java/android/webkit/JniUtil.java
@@ -37,7 +37,6 @@ class JniUtil {
// 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 @@ class JniUtil {
// 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 @@ class JniUtil {
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 @@ class JniUtil {
}
}
- /**
- * 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 @@ class JniUtil {
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 4d7ade517a60..000000000000
--- 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 ee9b9492ebeb..000000000000
--- 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 b2e4b131ffc6..000000000000
--- 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 7bcd50dd4677..000000000000
--- 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 24e0d11b0562..650310e219a4 100644
--- a/core/java/android/webkit/WebResourceResponse.java
+++ b/core/java/android/webkit/WebResourceResponse.java
@@ -26,24 +26,6 @@ import java.io.InputStream;
* 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 class WebResourceResponse {
public InputStream getData() {
return mInputStream;
}
-
- StreamLoader loader(LoadListener listener) {
- return new Loader(listener);
- }
}
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 6dc3be540f0e..98d59c43fdec 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -122,6 +122,7 @@ import java.io.FileOutputStream;
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;
@@ -382,21 +383,24 @@ public class WebView extends AbsoluteLayout
@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);
@@ -556,7 +560,7 @@ public class WebView extends AbsoluteLayout
sendCharacter(text.charAt(textLength - 1));
} else if (isCharacterDelete) {
sendDeleteKey();
- } else if (textLength != originalLength ||
+ } else if ((textLength != originalLength) ||
!TextUtils.regionMatches(text, 0, original, 0,
textLength)) {
// Send a message so that key strokes and text replacement
@@ -583,6 +587,9 @@ public class WebView extends AbsoluteLayout
for (KeyEvent event : events) {
sendKeyEvent(event);
}
+ } else {
+ Message msg = mPrivateHandler.obtainMessage(KEY_PRESS, (int) c, 0);
+ mPrivateHandler.sendMessage(msg);
}
}
@@ -908,14 +915,22 @@ public class WebView extends AbsoluteLayout
// know to handle Shift and arrows natively first
private boolean mAccessibilityScriptInjected;
+
+ /**
+ * How long the caret handle will last without being touched.
+ */
+ private static final long CARET_HANDLE_STAMINA_MS = 3000;
+
private Drawable mSelectHandleLeft;
private Drawable mSelectHandleRight;
+ private Drawable mSelectHandleCenter;
private Rect mSelectCursorBase = new Rect();
private int mSelectCursorBaseLayerId;
private Rect mSelectCursorExtent = new Rect();
private int mSelectCursorExtentLayerId;
private Rect mSelectDraggingCursor;
private Point mSelectDraggingOffset = new Point();
+ private boolean mIsCaretSelection;
static final int HANDLE_ID_START = 0;
static final int HANDLE_ID_END = 1;
static final int HANDLE_ID_BASE = 2;
@@ -1012,6 +1027,8 @@ public class WebView extends AbsoluteLayout
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;
+ static final int KEY_PRESS = 145;
private static final int FIRST_PACKAGE_MSG_ID = SCROLL_TO_MSG_ID;
private static final int LAST_PACKAGE_MSG_ID = HIT_TEST_RESULT;
@@ -1418,7 +1435,7 @@ public class WebView extends AbsoluteLayout
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();
@@ -2030,7 +2047,6 @@ public class WebView extends AbsoluteLayout
public static void enablePlatformNotifications() {
checkThread();
synchronized (WebView.class) {
- Network.enablePlatformNotifications();
sNotificationsEnabled = true;
Context context = JniUtil.getContext();
if (context != null)
@@ -2048,7 +2064,6 @@ public class WebView extends AbsoluteLayout
public static void disablePlatformNotifications() {
checkThread();
synchronized (WebView.class) {
- Network.disablePlatformNotifications();
sNotificationsEnabled = false;
Context context = JniUtil.getContext();
if (context != null)
@@ -5108,31 +5123,45 @@ public class WebView extends AbsoluteLayout
}
private void drawTextSelectionHandles(Canvas canvas) {
- if (mSelectHandleLeft == null) {
- mSelectHandleLeft = mContext.getResources().getDrawable(
- com.android.internal.R.drawable.text_select_handle_left);
- }
int[] handles = new int[4];
getSelectionHandles(handles);
int start_x = contentToViewDimension(handles[0]);
int start_y = contentToViewDimension(handles[1]);
int end_x = contentToViewDimension(handles[2]);
int end_y = contentToViewDimension(handles[3]);
- // Magic formula copied from TextView
- start_x -= (mSelectHandleLeft.getIntrinsicWidth() * 3) / 4;
- mSelectHandleLeft.setBounds(start_x, start_y,
- start_x + mSelectHandleLeft.getIntrinsicWidth(),
- start_y + mSelectHandleLeft.getIntrinsicHeight());
- if (mSelectHandleRight == null) {
- mSelectHandleRight = mContext.getResources().getDrawable(
- com.android.internal.R.drawable.text_select_handle_right);
+
+ if (mIsCaretSelection) {
+ if (mSelectHandleCenter == null) {
+ mSelectHandleCenter = mContext.getResources().getDrawable(
+ com.android.internal.R.drawable.text_select_handle_middle);
+ }
+ // Caret handle is centered
+ start_x -= (mSelectHandleCenter.getIntrinsicWidth() / 2);
+ mSelectHandleCenter.setBounds(start_x, start_y,
+ start_x + mSelectHandleCenter.getIntrinsicWidth(),
+ start_y + mSelectHandleCenter.getIntrinsicHeight());
+ mSelectHandleCenter.draw(canvas);
+ } else {
+ if (mSelectHandleLeft == null) {
+ mSelectHandleLeft = mContext.getResources().getDrawable(
+ com.android.internal.R.drawable.text_select_handle_left);
+ }
+ // Magic formula copied from TextView
+ start_x -= (mSelectHandleLeft.getIntrinsicWidth() * 3) / 4;
+ mSelectHandleLeft.setBounds(start_x, start_y,
+ start_x + mSelectHandleLeft.getIntrinsicWidth(),
+ start_y + mSelectHandleLeft.getIntrinsicHeight());
+ if (mSelectHandleRight == null) {
+ mSelectHandleRight = mContext.getResources().getDrawable(
+ com.android.internal.R.drawable.text_select_handle_right);
+ }
+ end_x -= mSelectHandleRight.getIntrinsicWidth() / 4;
+ mSelectHandleRight.setBounds(end_x, end_y,
+ end_x + mSelectHandleRight.getIntrinsicWidth(),
+ end_y + mSelectHandleRight.getIntrinsicHeight());
+ mSelectHandleLeft.draw(canvas);
+ mSelectHandleRight.draw(canvas);
}
- end_x -= mSelectHandleRight.getIntrinsicWidth() / 4;
- mSelectHandleRight.setBounds(end_x, end_y,
- end_x + mSelectHandleRight.getIntrinsicWidth(),
- end_y + mSelectHandleRight.getIntrinsicHeight());
- mSelectHandleLeft.draw(canvas);
- mSelectHandleRight.draw(canvas);
}
/**
@@ -5554,6 +5583,9 @@ public class WebView extends AbsoluteLayout
+ "keyCode=" + keyCode
+ ", " + event + ", unicode=" + event.getUnicodeChar());
}
+ if (mIsCaretSelection) {
+ selectionDone();
+ }
if (mBlockWebkitViewMessages) {
return false;
}
@@ -5866,6 +5898,7 @@ public class WebView extends AbsoluteLayout
private boolean startSelectActionMode() {
mSelectCallback = new SelectActionModeCallback();
+ mSelectCallback.setTextSelected(!mIsCaretSelection);
mSelectCallback.setWebView(this);
if (startActionMode(mSelectCallback) == null) {
// There is no ActionMode, so do not allow the user to modify a
@@ -5886,9 +5919,13 @@ public class WebView extends AbsoluteLayout
private boolean setupWebkitSelect() {
syncSelectionCursors();
- if (!startSelectActionMode()) {
- selectionDone();
- return false;
+ ClipboardManager cm = (ClipboardManager)(mContext
+ .getSystemService(Context.CLIPBOARD_SERVICE));
+ if (!mIsCaretSelection || cm.hasPrimaryClip()) {
+ if (!startSelectActionMode()) {
+ selectionDone();
+ return false;
+ }
}
mSelectingText = true;
mTouchMode = TOUCH_DRAG_MODE;
@@ -5897,6 +5934,9 @@ public class WebView extends AbsoluteLayout
private void updateWebkitSelection() {
int[] handles = null;
+ if (mIsCaretSelection) {
+ mSelectCursorExtent.set(mSelectCursorBase);
+ }
if (mSelectingText) {
handles = new int[4];
handles[0] = mSelectCursorBase.centerX();
@@ -5910,6 +5950,14 @@ public class WebView extends AbsoluteLayout
mWebViewCore.sendMessageAtFrontOfQueue(EventHub.SELECT_TEXT, handles);
}
+ private void resetCaretTimer() {
+ mPrivateHandler.removeMessages(CLEAR_CARET_HANDLE);
+ if (!mSelectionStarted) {
+ mPrivateHandler.sendEmptyMessageDelayed(CLEAR_CARET_HANDLE,
+ CARET_HANDLE_STAMINA_MS);
+ }
+ }
+
/**
* Use this method to put the WebView into text selection mode.
* Do not rely on this functionality; it will be deprecated in the future.
@@ -5937,9 +5985,14 @@ public class WebView extends AbsoluteLayout
mSelectingText = false;
// finish is idempotent, so this is fine even if selectionDone was
// called by mSelectCallback.onDestroyActionMode
- mSelectCallback.finish();
- mSelectCallback = null;
- updateWebkitSelection();
+ if (mSelectCallback != null) {
+ mSelectCallback.finish();
+ mSelectCallback = null;
+ }
+ if (!mIsCaretSelection) {
+ updateWebkitSelection();
+ }
+ mIsCaretSelection = false;
invalidate(); // redraw without selection
mAutoScrollX = 0;
mAutoScrollY = 0;
@@ -6553,18 +6606,26 @@ public class WebView extends AbsoluteLayout
(eventTime - mLastTouchUpTime), eventTime);
}
mSelectionStarted = false;
- if (mSelectingText && mSelectHandleLeft != null
- && mSelectHandleRight != null) {
+ if (mSelectingText) {
int shiftedY = y - getTitleHeight() + mScrollY;
int shiftedX = x + mScrollX;
- if (mSelectHandleLeft.getBounds()
+ if (mSelectHandleCenter != null && mSelectHandleCenter.getBounds()
.contains(shiftedX, shiftedY)) {
mSelectionStarted = true;
mSelectDraggingCursor = mSelectCursorBase;
- } else if (mSelectHandleRight.getBounds()
+ mPrivateHandler.removeMessages(CLEAR_CARET_HANDLE);
+ } else if (mSelectHandleLeft != null
+ && mSelectHandleLeft.getBounds()
+ .contains(shiftedX, shiftedY)) {
+ mSelectionStarted = true;
+ mSelectDraggingCursor = mSelectCursorBase;
+ } else if (mSelectHandleRight != null
+ && mSelectHandleRight.getBounds()
.contains(shiftedX, shiftedY)) {
mSelectionStarted = true;
mSelectDraggingCursor = mSelectCursorExtent;
+ } else if (mIsCaretSelection) {
+ selectionDone();
}
if (mSelectDraggingCursor != null) {
mSelectDraggingOffset.set(
@@ -7215,6 +7276,9 @@ public class WebView extends AbsoluteLayout
if (mSelectingText) {
mSelectionStarted = false;
+ if (mIsCaretSelection) {
+ resetCaretTimer();
+ }
syncSelectionCursors();
invalidate();
}
@@ -7857,7 +7921,10 @@ public class WebView extends AbsoluteLayout
}
}, 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
@@ -9095,24 +9162,7 @@ public class WebView extends AbsoluteLayout
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:
@@ -9173,6 +9223,13 @@ public class WebView extends AbsoluteLayout
}
break;
}
+ case CLEAR_CARET_HANDLE:
+ selectionDone();
+ break;
+
+ case KEY_PRESS:
+ mWebViewCore.sendMessage(EventHub.KEY_PRESS, msg.arg1);
+ break;
default:
super.handleMessage(msg);
@@ -9181,6 +9238,47 @@ public class WebView extends AbsoluteLayout
}
}
+ private void setHitTestResult(WebKitHitTest hit) {
+ if (hit == null) {
+ mInitialHitTestResult = null;
+ return;
+ }
+ 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) {
+ String substr = null;
+ if (hit.mIntentUrl.startsWith(SCHEME_GEO)) {
+ mInitialHitTestResult.mType = HitTestResult.GEO_TYPE;
+ substr = hit.mIntentUrl.substring(SCHEME_GEO.length());
+ } else if (hit.mIntentUrl.startsWith(SCHEME_TEL)) {
+ mInitialHitTestResult.mType = HitTestResult.PHONE_TYPE;
+ substr = hit.mIntentUrl.substring(SCHEME_TEL.length());
+ } else if (hit.mIntentUrl.startsWith(SCHEME_MAILTO)) {
+ mInitialHitTestResult.mType = HitTestResult.EMAIL_TYPE;
+ substr = hit.mIntentUrl.substring(SCHEME_MAILTO.length());
+ } else {
+ return;
+ }
+ try {
+ mInitialHitTestResult.mExtra = URLDecoder.decode(substr, "UTF-8");
+ } catch (Throwable 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;
@@ -9188,7 +9286,7 @@ public class WebView extends AbsoluteLayout
if (mTouchHighlightRegion.isEmpty()) {
return false;
}
- if (mFocusedNode.mHasFocus) {
+ if (mFocusedNode.mHasFocus && !isInTouchMode()) {
return !mFocusedNode.mEditable;
}
if (mInitialHitTestResult.mType == HitTestResult.UNKNOWN_TYPE) {
@@ -9423,14 +9521,19 @@ public class WebView extends AbsoluteLayout
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 84f0b9c845e5..e7da1a8c40e7 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -879,6 +879,7 @@ public final class WebViewCore {
static class WebKitHitTest {
String mLinkUrl;
+ String mIntentUrl;
String mAnchorText;
String mImageUrl;
String mAltDisplayString;
@@ -1172,8 +1173,11 @@ public final class WebViewCore {
static final int TRUST_STORAGE_UPDATED = 220;
// find-on-page controls
- static final int FIND_ALL = 220;
- static final int FIND_NEXT = 221;
+ static final int FIND_ALL = 221;
+ static final int FIND_NEXT = 222;
+
+ // key was pressed (down and up)
+ static final int KEY_PRESS = 223;
// Private handler for WebCore messages.
private Handler mHandler;
@@ -1354,6 +1358,10 @@ public final class WebViewCore {
key((KeyEvent) msg.obj, false);
break;
+ case KEY_PRESS:
+ keyPress(msg.arg1);
+ break;
+
case FAKE_CLICK:
nativeClick(mNativeClass, msg.arg1, msg.arg2, true);
break;
@@ -1402,21 +1410,12 @@ public final class WebViewCore {
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:
@@ -1490,14 +1489,10 @@ public final class WebViewCore {
}
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:
@@ -2073,6 +2068,11 @@ public final class WebViewCore {
}
}
+ private void keyPress(int unicodeChar) {
+ nativeKey(mNativeClass, 0, unicodeChar, 0, false, false, false, true);
+ nativeKey(mNativeClass, 0, unicodeChar, 0, false, false, false, false);
+ }
+
// These values are used to avoid requesting a layout based on old values
private int mCurrentViewWidth = 0;
private int mCurrentViewHeight = 0;
@@ -2445,14 +2445,6 @@ public final class WebViewCore {
// 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();
}
@@ -2834,7 +2826,7 @@ public final class WebViewCore {
// called by JNI
private void initEditField(int pointer, String text, int inputType,
boolean isSpellCheckEnabled, boolean nextFieldIsText,
- String label, int start, int end) {
+ String label, int start, int end, int selectionPtr) {
if (mWebView == null) {
return;
}
@@ -2844,7 +2836,7 @@ public final class WebViewCore {
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 695c154acba0..757a619ffe6b 100644
--- a/core/java/android/webkit/WebViewDatabase.java
+++ b/core/java/android/webkit/WebViewDatabase.java
@@ -31,9 +31,6 @@ import android.database.sqlite.SQLiteDatabase;
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 @@ public class WebViewDatabase {
// 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 @@ public class WebViewDatabase {
"_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 @@ public class WebViewDatabase {
// 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 @@ public class WebViewDatabase {
}
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 @@ public class WebViewDatabase {
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 @@ public class WebViewDatabase {
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 @@ public class WebViewDatabase {
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 @@ public class WebViewDatabase {
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 @@ public class WebViewDatabase {
+ ") 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 @@ public class WebViewDatabase {
}
//
- // 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 6a4ca2918f1c..000000000000
--- 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 2602523c283f..577444068bd7 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -1020,6 +1020,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
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 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
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 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
} 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 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
/**
+ * 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
diff --git a/core/java/android/widget/TableLayout.java b/core/java/android/widget/TableLayout.java
index f5d374656d89..b870ceeac4de 100644
--- a/core/java/android/widget/TableLayout.java
+++ b/core/java/android/widget/TableLayout.java
@@ -735,8 +735,7 @@ public class TableLayout extends LinearLayout {
* @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 f27f02d8db78..a4087d518514 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -232,25 +232,72 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
static final String LOG_TAG = "TextView";
static final boolean DEBUG_EXTRACT = false;
- private static final int PRIORITY = 100;
- private int mCurrentAlpha = 255;
+ // 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;
+ private static final int SERIF = 2;
+ private static final int MONOSPACE = 3;
- final int[] mTempCoords = new int[2];
- Rect mTempRect;
+ // Bitfield for the "numeric" XML parameter.
+ // TODO: How can we get this from the XML instead of hardcoding it here?
+ private static final int SIGNED = 2;
+ private static final int DECIMAL = 4;
+
+ private static enum TEXT_ALIGN {
+ INHERIT, GRAVITY, TEXT_START, TEXT_END, CENTER, VIEW_START, VIEW_END;
+ }
+
+ /**
+ * Draw marquee text with fading edges as usual
+ */
+ private static final int MARQUEE_FADE_NORMAL = 0;
+
+ /**
+ * Draw marquee text as ellipsize end while inactive instead of with the fade.
+ * (Useful for devices where the fade can be expensive if overdone)
+ */
+ private static final int MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS = 1;
+
+ /**
+ * Draw marquee text with fading edges because it is currently active/animating.
+ */
+ 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 int mCurTextColor;
private ColorStateList mHintTextColor;
private ColorStateList mLinkTextColor;
+ private int mCurTextColor;
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();
@@ -258,18 +305,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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;
- private static final int SERIF = 2;
- private static final int MONOSPACE = 3;
-
- // Bitfield for the "numeric" XML parameter.
- // TODO: How can we get this from the XML instead of hardcoding it here?
- private static final int SIGNED = 2;
- private static final int DECIMAL = 4;
+ private TextUtils.TruncateAt mEllipsize;
static class Drawables {
final Rect mCompoundRect = new Rect();
@@ -283,96 +319,20 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
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 CharWrapper mCharWrapper;
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 {
- INHERIT, GRAVITY, TEXT_START, TEXT_END, CENTER, VIEW_START, VIEW_END;
- }
+ private TEXT_ALIGN mTextAlign = TEXT_ALIGN.INHERIT;
- private boolean mResolvedDrawables = false;
+ private boolean mResolvedDrawables;
/**
* On some devices the fading edges add a performance penalty if used
@@ -387,21 +347,84 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
private Layout mSavedMarqueeModeLayout;
- /**
- * Draw marquee text with fading edges as usual
- */
- private static final int MARQUEE_FADE_NORMAL = 0;
+ @ViewDebug.ExportedProperty(category = "text")
+ private CharSequence mText;
+ private CharSequence mTransformed;
+ private BufferType mBufferType = BufferType.NORMAL;
- /**
- * Draw marquee text as ellipsize end while inactive instead of with the fade.
- * (Useful for devices where the fade can be expensive if overdone)
- */
- private static final int MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS = 1;
+ 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;
/**
- * Draw marquee text with fading edges because it is currently active/animating.
+ * EditText specific data, created on demand when one of the Editor fields is used.
+ * See {@link #createEditorIfNeeded(String)}.
*/
- private static final int MARQUEE_FADE_SWITCH_SHOW_FADE = 2;
+ private Editor mEditor;
/*
* Kick-start the font cache for the zygote process (to pay the cost of
@@ -454,14 +477,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
- // 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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* 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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
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
+
+ createEditorIfNeeded("setKeyListenerOnly");
+ if (getEditor().mKeyListener != input) {
+ getEditor().mKeyListener = input;
+ if (input != null && !(mText instanceof Editable)) {
+ setText(mText);
+ }
- setFilters((Editable) mText, mFilters);
+ setFilters((Editable) mText, mFilters);
+ }
}
/**
@@ -1384,19 +1396,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* 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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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 class TextView extends View implements ViewTreeObserver.OnPreDrawListener
public void setHighlightColor(int color) {
if (mHighlightColor != color) {
mHighlightColor = color;
- mTextDisplayListIsValid = false;
+ if (mEditor != null) getEditor().mTextDisplayListIsValid = false;
invalidate();
}
}
@@ -2332,7 +2347,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mShadowDx = dx;
mShadowDy = dy;
- mTextDisplayListIsValid = false;
+ if (mEditor != null) getEditor().mTextDisplayListIsValid = false;
invalidate();
}
@@ -2824,7 +2839,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
if (inval) {
- mTextDisplayListIsValid = false;
+ if (mEditor != null) getEditor().mTextDisplayListIsValid = false;
invalidate();
}
}
@@ -2862,73 +2877,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
- /**
- * 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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
ss.frozenWithFocus = true;
}
- ss.error = mError;
+ ss.error = getError();
return ss;
}
@@ -3034,7 +2984,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
ss.selEnd);
if (ss.frozenWithFocus) {
- mFrozenWithFocus = true;
+ createEditorIfNeeded("restore instance with focus");
+ getEditor().mFrozenWithFocus = true;
}
}
}
@@ -3192,7 +3143,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* 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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
// 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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* 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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* 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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* 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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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);
- }
-
- TextView tv = (TextView) mPopup.getContentView();
- chooseSize(mPopup, mError, tv);
- tv.setText(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;
+ getEditor().mErrorPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
}
- @Override
- public void update(int x, int y, int w, int h, boolean force) {
- super.update(x, y, w, h, force);
+ TextView tv = (TextView) getEditor().mErrorPopup.getContentView();
+ chooseSize(getEditor().mErrorPopup, getEditor().mError, tv);
+ tv.setText(getEditor().mError);
- boolean above = isAboveAnchor();
- if (above != mAbove) {
- fixDirection(above);
- }
- }
+ getEditor().mErrorPopup.showAsDropDown(this, getErrorX(), getErrorY());
+ getEditor().mErrorPopup.fixDirection(getEditor().mErrorPopup.isAboveAnchor());
}
/**
@@ -4060,7 +3879,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
/**
* 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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* 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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* 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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// 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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
Layout layout = mLayout;
- int cursorcolor = color;
if (mHint != null && mText.length() == 0) {
if (mHintTextColor != null) {
@@ -4870,14 +4643,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
- 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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private void updateCursorsPositions() {
if (mCursorDrawableRes == 0) {
- mCursorCount = 0;
+ getEditor().mCursorCount = 0;
return;
}
@@ -5058,40 +4692,39 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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();
}
}
}
@@ -5234,7 +4872,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
@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) {
@@ -5292,14 +4930,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// 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);
@@ -5317,7 +4957,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* lines but where it doesn't make sense to insert newlines.
*/
private boolean shouldAdvanceFocusOnEnter() {
- if (mInput == null) {
+ if (getKeyListener() == null) {
return false;
}
@@ -5325,8 +4965,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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;
@@ -5341,9 +4981,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* 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;
@@ -5365,13 +5005,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// 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;
}
@@ -5408,21 +5048,21 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// 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) {
@@ -5438,7 +5078,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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;
@@ -5484,14 +5124,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* 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);
}
}
@@ -5529,11 +5169,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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;
}
@@ -5584,8 +5224,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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)
@@ -5597,22 +5237,23 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
@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();
- }
- 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;
+ if (getEditor().mInputMethodState == null) {
+ getEditor().mInputMethodState = new InputMethodState();
+ }
+ 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;
}
@@ -5646,7 +5287,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
InputConnection ic = new EditableInputConnection(this);
outAttrs.initialSelStart = getSelectionStart();
outAttrs.initialSelEnd = getSelectionEnd();
- outAttrs.initialCapsMode = ic.getCursorCapsMode(mInputType);
+ outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
return ic;
}
}
@@ -5738,13 +5379,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
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) {
@@ -5760,8 +5401,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
+ 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;
@@ -5838,8 +5478,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @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
@@ -5870,109 +5510,20 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @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);
- }
-
- 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);
- }
- }
+ getEditor().mCorrectionHighlighter.invalidate(false);
}
- 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) {
@@ -5994,8 +5545,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
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) {
@@ -6005,7 +5557,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
void ensureEndedBatchEdit() {
- final InputMethodState ims = mInputMethodState;
+ final InputMethodState ims = getEditor().mInputMethodState;
if (ims != null && ims.mBatchEditNesting != 0) {
ims.mBatchEditNesting = 0;
finishBatchEdit(ims);
@@ -6033,7 +5585,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
if (curs >= 0) {
- mHighlightPathBogus = true;
+ getEditor().mHighlightPathBogus = true;
makeBlink();
bringPointIntoView(curs);
}
@@ -6048,7 +5600,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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}.
@@ -6113,8 +5665,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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;
}
}
@@ -6122,7 +5674,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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
@@ -6190,7 +5742,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mOldMaximum = mMaximum;
mOldMaxMode = mMaxMode;
- mHighlightPathBogus = true;
+ if (mEditor != null) getEditor().mHighlightPathBogus = true;
if (wantWidth < 0) {
wantWidth = 0;
@@ -6200,7 +5752,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
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;
@@ -6317,7 +5869,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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) {
@@ -6778,7 +6330,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
@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;
}
/**
@@ -7004,11 +6556,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// 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);
@@ -7228,11 +6780,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @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;
}
}
}
@@ -7311,7 +6863,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
@android.view.RemotableViewMethod
public void setSelectAllOnFocus(boolean selectAllOnFocus) {
- mSelectAllOnFocus = selectAllOnFocus;
+ createEditorIfNeeded("setSelectAllOnFocus");
+ getEditor().mSelectAllOnFocus = selectAllOnFocus;
if (selectAllOnFocus && !(mText instanceof Spannable)) {
setText(mText, BufferType.SPANNABLE);
@@ -7325,8 +6878,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
@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();
@@ -7337,7 +6892,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
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() {
@@ -7349,7 +6905,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private void startMarquee() {
// Do not ellipsize EditText
- if (mInput != null) return;
+ if (getKeyListener() != null) return;
if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
return;
@@ -7399,142 +6955,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
- 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.
@@ -7563,7 +6983,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
protected void onSelectionChanged(int selStart, int selEnd) {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
- mTextDisplayListIsValid = false;
+ getEditor().mTextDisplayListIsValid = false;
}
/**
@@ -7642,13 +7062,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
- 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);
}
/**
@@ -7670,7 +7084,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* 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();
}
@@ -7689,7 +7103,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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.
@@ -7700,18 +7114,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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();
@@ -7720,14 +7129,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
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);
@@ -7735,6 +7139,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
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);
@@ -7750,16 +7159,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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;
}
@@ -7803,8 +7212,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
- 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);
}
}
@@ -7813,289 +7222,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
private void updateSpellCheckSpans(int start, int end, boolean createSpellChecker) {
if (isTextEditable() && isSuggestionsEnabled() && !(this instanceof ExtractEditText)) {
- if (mSpellChecker == null && createSpellChecker) {
- mSpellChecker = new SpellChecker(this);
- }
- if (mSpellChecker != null) {
- 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);
- }
+ if (getEditor().mSpellChecker == null && createSpellChecker) {
+ getEditor().mSpellChecker = new SpellChecker(this);
}
- }
-
- /**
- * 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];
+ if (getEditor().mSpellChecker != null) {
+ getEditor().mSpellChecker.spellCheck(start, end);
}
}
}
/**
- * 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
@@ -8115,7 +7251,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// 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
@@ -8133,95 +7269,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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);
@@ -8233,48 +7288,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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);
}
@@ -8282,7 +7300,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
@Override
protected void onVisibilityChanged(View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
- if (visibility != VISIBLE) {
+ if (mEditor != null && visibility != VISIBLE) {
hideControllers();
}
}
@@ -8317,23 +7335,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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);
@@ -8342,13 +7344,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* 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) {
@@ -8358,7 +7360,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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.
@@ -8371,34 +7374,33 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
- 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();
@@ -8481,6 +7483,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
private void prepareCursorControllers() {
+ if (mEditor == null) return;
+
boolean windowSupportsHandles = false;
ViewGroup.LayoutParams params = getRootView().getLayoutParams();
@@ -8490,23 +7494,23 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|| 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;
}
}
}
@@ -8526,19 +7530,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* 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;
}
@@ -8551,49 +7554,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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;
@@ -8606,12 +7571,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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);
}
}
@@ -8810,7 +7775,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// 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() {
@@ -8818,7 +7783,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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;
}
@@ -8839,7 +7804,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private boolean canPaste() {
return (mText instanceof Editable &&
- mInput != null &&
+ mEditor != null && getEditor().mKeyListener != null &&
getSelectionStart() >= 0 &&
getSelectionEnd() >= 0 &&
((ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE)).
@@ -8880,8 +7845,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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 ||
@@ -8951,17 +7917,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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) {
@@ -9215,17 +8181,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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;
@@ -9236,9 +8191,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
// 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();
@@ -9246,7 +8201,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
vibrate = false;
}
- if (!handled && mSelectionActionMode != null) {
+ if (!handled && (mEditor == null || getEditor().mSelectionActionMode != null)) {
if (touchPositionIsInSelection()) {
// Start a drag
final int start = getSelectionStart();
@@ -9273,8 +8228,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
}
- if (handled) {
- mDiscardNextActionUp = true;
+ if (handled && mEditor != null) {
+ getEditor().mDiscardNextActionUp = true;
}
return handled;
@@ -9303,10 +8258,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
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 {
@@ -9314,6 +8269,1420 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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)
@@ -9326,6 +9695,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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) {
@@ -9404,62 +9774,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
- 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;
@@ -9587,7 +9901,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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()) {
@@ -9597,7 +9911,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
public SuggestionsPopupWindow() {
- mCursorWasVisibleBeforeSuggestions = mCursorVisible;
+ mCursorWasVisibleBeforeSuggestions = getEditor().mCursorVisible;
mSuggestionSpanComparator = new SuggestionSpanComparator();
mSpansLengths = new HashMap<SuggestionSpan, Integer>();
}
@@ -9735,7 +10049,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (!(mText instanceof Editable)) return;
if (updateSuggestions()) {
- mCursorWasVisibleBeforeSuggestions = mCursorVisible;
+ mCursorWasVisibleBeforeSuggestions = getEditor().mCursorVisible;
setCursorVisible(false);
mIsShowingUp = true;
super.show();
@@ -9890,17 +10204,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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();
@@ -9933,8 +10247,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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() &&
@@ -10034,209 +10348,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
/**
- * 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
@@ -10298,8 +10409,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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;
}
@@ -10315,16 +10426,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
@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());
@@ -10332,16 +10443,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
@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;
}
}
@@ -10355,7 +10466,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
protected void createPopupWindow() {
mPopupWindow = new PopupWindow(TextView.this.mContext, null,
com.android.internal.R.attr.textSelectHandleWindowStyle);
- mPopupWindow.setClippingEnabled(true);
+ mPopupWindow.setClippingEnabled(true);
}
@Override
@@ -10755,7 +10866,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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);
}
@@ -10769,13 +10880,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
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);
}
@@ -10996,12 +11108,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
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;
}
@@ -11042,12 +11154,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
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);
}
}
@@ -11055,10 +11167,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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();
@@ -11103,7 +11215,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (stayedInArea && isPositionOnText(x, y)) {
startSelectionActionMode();
- mDiscardNextActionUp = true;
+ getEditor().mDiscardNextActionUp = true;
}
}
}
@@ -11192,461 +11304,501 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
- 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 class Editor {
+ Editor() {
+ mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ final CompatibilityInfo compat = TextView.this.getResources().getCompatibilityInfo();
+ mHighlightPaint.setCompatibilityScaling(compat.applicationScale);
}
- }
- 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();
- }
+ // Cursor Controllers.
+ InsertionPointCursorController mInsertionPointCursorController;
+ SelectionModifierCursorController mSelectionModifierCursorController;
+ ActionMode mSelectionActionMode;
+ boolean mInsertionControllerEnabled;
+ boolean mSelectionControllerEnabled;
- /**
- * 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;
- }
+ // Used to highlight a word when it is corrected by the IME
+ CorrectionHighlighter mCorrectionHighlighter;
- 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;
- }
+ InputContentType mInputContentType;
+ InputMethodState mInputMethodState;
- 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);
- }
+ Path mHighlightPath;
+ boolean mHighlightPathBogus = true;
+ final Paint mHighlightPaint;
- private int getOffsetAtCoordinate(int line, float x) {
- x = convertToLocalHorizontalCoordinate(x);
- return getLayout().getOffsetForHorizontal(line, x);
- }
+ DisplayList mTextDisplayList;
+ boolean mTextDisplayListIsValid;
- /** 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;
+ boolean mFrozenWithFocus;
+ boolean mSelectionMoved;
+ boolean mTouchFocusSelected;
- final int line = getLineAtCoordinate(y);
- x = convertToLocalHorizontalCoordinate(x);
+ KeyListener mKeyListener;
+ int mInputType = EditorInfo.TYPE_NULL;
- if (x < getLayout().getLineLeft(line)) return false;
- if (x > getLayout().getLineRight(line)) return false;
- return true;
- }
+ boolean mDiscardNextActionUp;
+ boolean mIgnoreActionUpEvent;
- @Override
- public boolean onDragEvent(DragEvent event) {
- switch (event.getAction()) {
- case DragEvent.ACTION_DRAG_STARTED:
- return hasInsertionController();
+ long mShowCursor;
+ Blink mBlink;
- case DragEvent.ACTION_DRAG_ENTERED:
- TextView.this.requestFocus();
- return true;
+ boolean mCursorVisible = true;
+ boolean mSelectAllOnFocus;
+ boolean mTextIsSelectable;
- case DragEvent.ACTION_DRAG_LOCATION:
- final int offset = getOffsetForPosition(event.getX(), event.getY());
- Selection.setSelection((Spannable)mText, offset);
- return true;
+ 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;
- case DragEvent.ACTION_DROP:
- onDrop(event);
- return true;
+ boolean mInBatchEditControllers;
- case DragEvent.ACTION_DRAG_ENDED:
- case DragEvent.ACTION_DRAG_EXITED:
- default:
- return true;
- }
- }
+ SuggestionsPopupWindow mSuggestionsPopupWindow;
+ SuggestionRangeSpan mSuggestionRangeSpan;
+ Runnable mShowSuggestionRunnable;
- 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 Drawable[] mCursorDrawable = new Drawable[2];
+ int mCursorCount; // Actual current number of used mCursorDrawable: 0, 1 or 2 (split)
- final int offset = getOffsetForPosition(event.getX(), event.getY());
+ Drawable mSelectHandleLeft;
+ Drawable mSelectHandleRight;
+ Drawable mSelectHandleCenter;
- Object localState = event.getLocalState();
- DragLocalState dragLocalState = null;
- if (localState instanceof DragLocalState) {
- dragLocalState = (DragLocalState) localState;
- }
- boolean dragDropIntoItself = dragLocalState != null &&
- dragLocalState.sourceTextView == this;
+ // Global listener that detects changes in the global position of the TextView
+ PositionListener mPositionListener;
- if (dragDropIntoItself) {
- if (offset >= dragLocalState.start && offset < dragLocalState.end) {
- // A drop inside the original selection discards the drop.
- return;
+ 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();
+ // 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 */);
}
- final int originalLength = mText.length();
- long minMax = prepareSpacesAroundPaste(offset, offset, content);
- int min = extractRangeStartFromLong(minMax);
- int max = extractRangeEndFromLong(minMax);
+ void onDetachedFromWindow() {
+ if (mError != null) {
+ hideError();
+ }
- Selection.setSelection((Spannable) mText, max);
- replaceText_internal(min, max, content);
+ if (mBlink != null) {
+ mBlink.removeCallbacks(mBlink);
+ }
- 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;
+ if (mInsertionPointCursorController != null) {
+ mInsertionPointCursorController.onDetached();
}
- // Delete original selection
- deleteText_internal(dragSourceStart, dragSourceEnd);
+ if (mSelectionModifierCursorController != null) {
+ mSelectionModifierCursorController.onDetached();
+ }
- // 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);
+ if (mShowSuggestionRunnable != null) {
+ removeCallbacks(mShowSuggestionRunnable);
}
- }
- }
- /**
- * @return True if this view supports insertion handles.
- */
- boolean hasInsertionController() {
- return mInsertionControllerEnabled;
- }
+ if (mTextDisplayList != null) {
+ mTextDisplayList.invalidate();
+ }
- /**
- * @return True if this view supports selection handles.
- */
- boolean hasSelectionController() {
- return mSelectionControllerEnabled;
- }
+ 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;
+ }
- InsertionPointCursorController getInsertionController() {
- if (!mInsertionControllerEnabled) {
- return null;
+ hideControllers();
}
- if (mInsertionPointCursorController == null) {
- mInsertionPointCursorController = new InsertionPointCursorController();
-
- final ViewTreeObserver observer = getViewTreeObserver();
- observer.addOnTouchModeChangeListener(mInsertionPointCursorController);
+ 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 (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;
+ }
+ }
}
- return mInsertionPointCursorController;
- }
-
- SelectionModifierCursorController getSelectionController() {
- if (!mSelectionControllerEnabled) {
- return null;
+ void setFrame() {
+ if (mErrorPopup != null) {
+ TextView tv = (TextView) mErrorPopup.getContentView();
+ chooseSize(mErrorPopup, mError, tv);
+ mErrorPopup.update(TextView.this, getErrorX(), getErrorY(),
+ mErrorPopup.getWidth(), mErrorPopup.getHeight());
+ }
}
- if (mSelectionModifierCursorController == null) {
- mSelectionModifierCursorController = new SelectionModifierCursorController();
-
- final ViewTreeObserver observer = getViewTreeObserver();
- observer.addOnTouchModeChangeListener(mSelectionModifierCursorController);
- }
+ void onFocusChanged(boolean focused, int direction) {
+ mShowCursor = SystemClock.uptimeMillis();
+ ensureEndedBatchEdit();
- return mSelectionModifierCursorController;
- }
+ if (focused) {
+ int selStart = getSelectionStart();
+ int selEnd = getSelectionEnd();
- boolean isInBatchEditMode() {
- final InputMethodState ims = mInputMethodState;
- if (ims != null) {
- return ims.mBatchEditNesting > 0;
- }
- return mInBatchEditControllers;
- }
+ // 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();
- @Override
- public void onResolveTextDirection() {
- if (hasPasswordTransformationMethod()) {
- mTextDir = TextDirectionHeuristics.LOCALE;
- return;
- }
+ mCreatedWithASelection = mFrozenWithFocus && hasSelection() && !isFocusHighlighted;
- // Always need to resolve layout direction first
- final boolean defaultIsRtl = (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL);
+ 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);
+ }
- // 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;
- }
- }
+ // Note this may have to be moved out of the Editor class
+ if (mMovement != null) {
+ mMovement.onTakeFocus(TextView.this, (Spannable) mText, direction);
+ }
- /**
- * 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;
- }
+ // 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);
+ }
- Drawables dr = mDrawables;
- switch(getResolvedLayoutDirection()) {
- case LAYOUT_DIRECTION_RTL:
- if (dr.mDrawableStart != null) {
- dr.mDrawableRight = dr.mDrawableStart;
+ if (mSelectAllOnFocus) {
+ selectAll();
+ }
- dr.mDrawableSizeRight = dr.mDrawableSizeStart;
- dr.mDrawableHeightRight = dr.mDrawableHeightStart;
+ mTouchFocusSelected = true;
}
- if (dr.mDrawableEnd != null) {
- dr.mDrawableLeft = dr.mDrawableEnd;
- dr.mDrawableSizeLeft = dr.mDrawableSizeEnd;
- dr.mDrawableHeightLeft = dr.mDrawableHeightEnd;
- }
- break;
+ mFrozenWithFocus = false;
+ mSelectionMoved = false;
- case LAYOUT_DIRECTION_LTR:
- default:
- if (dr.mDrawableStart != null) {
- dr.mDrawableLeft = dr.mDrawableStart;
+ if (mError != null) {
+ showError();
+ }
- dr.mDrawableSizeLeft = dr.mDrawableSizeStart;
- dr.mDrawableHeightLeft = dr.mDrawableHeightStart;
+ 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();
}
- if (dr.mDrawableEnd != null) {
- dr.mDrawableRight = dr.mDrawableEnd;
- dr.mDrawableSizeRight = dr.mDrawableSizeEnd;
- dr.mDrawableHeightRight = dr.mDrawableHeightEnd;
+ // No need to create the controller
+ if (mSelectionModifierCursorController != null) {
+ mSelectionModifierCursorController.resetTouchOffsets();
}
- break;
+ }
}
- mResolvedDrawables = true;
- }
- protected void resetResolvedDrawables() {
- mResolvedDrawables = false;
- }
+ void sendOnTextChanged(int start, int after) {
+ updateSpellCheckSpans(start, start + after, false);
+ mTextDisplayListIsValid = false;
- /**
- * @hide
- */
- protected void viewClicked(InputMethodManager imm) {
- if (imm != null) {
- imm.viewClicked(this);
+ // 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();
}
- }
-
- /**
- * 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);
- }
+ 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;
+ }
+ }
- /**
- * 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);
- }
+ return -1;
+ }
- /**
- * 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);
- }
+ 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();
+ }
- @ViewDebug.ExportedProperty(category = "text")
- private CharSequence mText;
- private CharSequence mTransformed;
- private BufferType mBufferType = BufferType.NORMAL;
+ // Don't leave us in the middle of a batch edit.
+ onEndBatchEdit();
+ }
+ }
- private int mInputType = EditorInfo.TYPE_NULL;
- private CharSequence mHint;
- private Layout mHintLayout;
+ void onTouchEvent(MotionEvent event) {
+ if (hasSelectionController()) {
+ getSelectionController().onTouchEvent(event);
+ }
- private KeyListener mInput;
+ if (mShowSuggestionRunnable != null) {
+ removeCallbacks(mShowSuggestionRunnable);
+ mShowSuggestionRunnable = null;
+ }
- private MovementMethod mMovement;
- private TransformationMethod mTransformation;
- private boolean mAllowTransformationLengthChange;
- private ChangeWatcher mChangeWatcher;
+ if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ mLastDownPositionX = event.getX();
+ mLastDownPositionY = event.getY();
- private ArrayList<TextWatcher> mListeners = null;
+ // Reset this state; it will be re-set if super.onTouchEvent
+ // causes focus to move to the view.
+ mTouchFocusSelected = false;
+ mIgnoreActionUpEvent = false;
+ }
+ }
- // display attributes
- private final TextPaint mTextPaint;
- private boolean mUserSetTextScaleX;
- private final Paint mHighlightPaint;
- private int mHighlightColor = 0x6633B5E5;
- private Layout mLayout;
+ void onDraw(Canvas canvas, Layout layout, int cursorOffsetVertical) {
+ Path highlight = null;
+ Paint highlightPaint = null;
- private long mShowCursor;
- private Blink mBlink;
- private boolean mCursorVisible = true;
+ int selStart = -1, selEnd = -1;
+ boolean drawCursor = false;
- // Cursor Controllers.
- private InsertionPointCursorController mInsertionPointCursorController;
- private SelectionModifierCursorController mSelectionModifierCursorController;
- private ActionMode mSelectionActionMode;
- private boolean mInsertionControllerEnabled;
- private boolean mSelectionControllerEnabled;
- private boolean mInBatchEditControllers;
+ 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();
- private boolean mSelectAllOnFocus = false;
+ if (selStart >= 0) {
+ if (mHighlightPath == null) mHighlightPath = new Path();
- private int mGravity = Gravity.TOP | Gravity.START;
- private boolean mHorizontallyScrolling;
+ if (selStart == selEnd) {
+ if (isCursorVisible() &&
+ (SystemClock.uptimeMillis() - mShowCursor) % (2 * BLINK) < BLINK) {
+ if (mHighlightPathBogus) {
+ mHighlightPath.reset();
+ mLayout.getCursorPath(selStart, mHighlightPath, mText);
+ updateCursorsPositions();
+ mHighlightPathBogus = false;
+ }
- private int mAutoLinkMask;
- private boolean mLinksClickable = true;
+ // 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;
+ }
- private float mSpacingMult = 1.0f;
- private float mSpacingAdd = 0.0f;
- private boolean mTextIsSelectable = 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);
- private static final int LINES = 1;
- private static final int EMS = LINES;
- private static final int PIXELS = 2;
+ highlight = mHighlightPath;
+ }
+ }
+ }
- private int mMaximum = Integer.MAX_VALUE;
- private int mMaxMode = LINES;
- private int mMinimum = 0;
- private int mMinMode = LINES;
+ 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);
+ }
+ }
- private int mOldMaximum = mMaximum;
- private int mOldMaxMode = mMaxMode;
+ if (imm.isWatchingCursor(TextView.this) && highlight != null) {
+ highlight.computeBounds(ims.mTmpRectF, true);
+ ims.mTmpOffset[0] = ims.mTmpOffset[1] = 0;
- private int mMaxWidth = Integer.MAX_VALUE;
- private int mMaxWidthMode = PIXELS;
- private int mMinWidth = 0;
- private int mMinWidthMode = PIXELS;
+ canvas.getMatrix().mapPoints(ims.mTmpOffset);
+ ims.mTmpRectF.offset(ims.mTmpOffset[0], ims.mTmpOffset[1]);
- private boolean mSingleLine;
- private int mDesiredHeightAtMeasure = -1;
- private boolean mIncludePad = true;
+ ims.mTmpRectF.offset(0, cursorOffsetVertical);
- // 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];
+ 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));
- // XXX should be much larger
- private static final int VERY_WIDE = 1024*1024;
+ imm.updateCursor(TextView.this,
+ ims.mCursorRectInWindow.left, ims.mCursorRectInWindow.top,
+ ims.mCursorRectInWindow.right, ims.mCursorRectInWindow.bottom);
+ }
+ }
+ }
- private static final int BLINK = 500;
+ if (mCorrectionHighlighter != null) {
+ mCorrectionHighlighter.draw(canvas, cursorOffsetVertical);
+ }
- private static final int ANIMATED_SCROLL_GAP = 250;
- private long mLastScroll;
- private Scroller mScroller = null;
+ 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;
+ }
- private BoringLayout.Metrics mBoring;
- private BoringLayout.Metrics mHintBoring;
+ if (canHaveDisplayList() && canvas.isHardwareAccelerated()) {
+ final int width = mRight - mLeft;
+ final int height = mBottom - mTop;
- private BoringLayout mSavedLayout, mSavedHintLayout;
+ if (mTextDisplayList == null || !mTextDisplayList.isValid() ||
+ !mTextDisplayListIsValid) {
+ if (mTextDisplayList == null) {
+ mTextDisplayList = getHardwareRenderer().createDisplayList("Text");
+ }
- private TextDirectionHeuristic mTextDir = null;
+ 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);
+ }
- 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 };
+ if (mMarquee != null && mMarquee.shouldDrawGhost()) {
+ canvas.translate((int) mMarquee.getGhostOffset(), 0.0f);
+ layout.draw(canvas, highlight, highlightPaint, cursorOffsetVertical);
+ }
+ }
+ }
}
diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java
index 8f10fff919bd..ef1d7d01bf36 100644
--- a/core/java/android/widget/TimePicker.java
+++ b/core/java/android/widget/TimePicker.java
@@ -213,6 +213,7 @@ public class TimePicker extends FrameLayout {
button.requestFocus();
mIsAm = !mIsAm;
updateAmPmControl();
+ onTimeChanged();
}
});
} else {
@@ -227,6 +228,7 @@ public class TimePicker extends FrameLayout {
picker.requestFocus();
mIsAm = !mIsAm;
updateAmPmControl();
+ onTimeChanged();
}
});
mAmPmSpinnerInput = (EditText) mAmPmSpinner.findViewById(R.id.numberpicker_input);
diff --git a/core/java/com/android/internal/widget/ActionBarContextView.java b/core/java/com/android/internal/widget/ActionBarContextView.java
index ed02636f1c6a..fa165272de7c 100644
--- a/core/java/com/android/internal/widget/ActionBarContextView.java
+++ b/core/java/com/android/internal/widget/ActionBarContextView.java
@@ -405,8 +405,7 @@ public class ActionBarContextView extends AbsActionBarView implements AnimatorLi
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 @@ public class ActionBarContextView extends AbsActionBarView implements AnimatorLi
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/google/android/mms/ContentType.java b/core/java/com/google/android/mms/ContentType.java
index b066fadae2f1..12a134399c33 100644
--- a/core/java/com/google/android/mms/ContentType.java
+++ b/core/java/com/google/android/mms/ContentType.java
@@ -39,6 +39,7 @@ public class ContentType {
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 class ContentType {
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 @@ public class ContentType {
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 @@ public class ContentType {
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 @@ public class ContentType {
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 @@ public class ContentType {
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 8940945a605a..d426f89bcb6c 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 @@ public class PduComposer {
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 8d57e5df5b0c..7037b6128c29 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 @@ public class PduPersister {
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/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index 18fb40584a08..bb175e71d9ff 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -764,7 +764,7 @@
<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>
- <string name="permlab_accessContentProvidersExternally" msgid="5077774297943409285">"ይዘት አቅራቢዎች በውጭ በኩል ድረስባቸው"</string>
+ <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>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 89f51c8a768f..58df147dbd8e 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -764,8 +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>
- <string name="permlab_accessContentProvidersExternally" msgid="5077774297943409285">"acceder a proveedores de contenido externamente"</string>
- <string name="permdesc_accessContentProvidersExternally" msgid="4544346486697853685">"Permite a su titular a acceder a los proveedores de contenido desde el shell. Las aplicaciones normales nunca deberían necesitarlo."</string>
+ <string name="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-sl/strings.xml b/core/res/res/values-sl/strings.xml
index 81e1e6f24082..159d5040bc45 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -765,7 +765,7 @@
<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>
<string name="permlab_accessContentProvidersExternally" msgid="5077774297943409285">"zunanji dostop do ponudnikov vsebine"</string>
- <string name="permdesc_accessContentProvidersExternally" msgid="4544346486697853685">"Omogoča imetniku, da dostopa do ponudnikov vsebine iz lupine. Naj ne bi bilo nikoli potrebno za običajne programe"</string>
+ <string name="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-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 9005a9b50e05..4cb3a3aacf6a 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>
@@ -765,7 +765,7 @@
<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>
<string name="permlab_accessContentProvidersExternally" msgid="5077774297943409285">"fikia watoa huduma nje"</string>
- <string name="permdesc_accessContentProvidersExternally" msgid="4544346486697853685">"Inaruhusu mmiliki kufikia watoa huduma kutoka kwa onyesho. Haifai kuhitajika kwa programu za kawaida."</string>
+ <string name="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-zu/strings.xml b/core/res/res/values-zu/strings.xml
index b3cb9a07ca05..282fb91d7804 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -764,7 +764,7 @@
<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>
- <string name="permlab_accessContentProvidersExternally" msgid="5077774297943409285">"finyelela ngaphandle abahlinzeki bokuqukethwe"</string>
+ <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>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index c152b73386bc..93757303a9ae 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2999,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" />
@@ -3009,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" />
@@ -3040,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/public.xml b/core/res/res/values/public.xml
index 2a6cef3f56b9..0950bdbe4b44 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -962,6 +962,7 @@
<java-symbol type="drawable" name="tab_bottom_right_v4" />
<java-symbol type="drawable" name="tab_indicator_v4" />
<java-symbol type="drawable" name="text_select_handle_left" />
+ <java-symbol type="drawable" name="text_select_handle_middle" />
<java-symbol type="drawable" name="text_select_handle_right" />
<java-symbol type="drawable" name="unknown_image" />
<java-symbol type="drawable" name="unlock_default" />
@@ -1541,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/tests/bandwidthtests/src/com/android/bandwidthtest/util/ConnectionUtil.java b/core/tests/bandwidthtests/src/com/android/bandwidthtest/util/ConnectionUtil.java
index 7499f68f87f6..8d778c46b5cf 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;
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 @@ public class ConnectionUtil {
} 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/docs/html/images/training/lint_icon.png b/docs/html/images/training/lint_icon.png
new file mode 100644
index 000000000000..118a7419c6bd
--- /dev/null
+++ b/docs/html/images/training/lint_icon.png
Binary files differ
diff --git a/docs/html/sdk/ndk/overview.jd b/docs/html/sdk/ndk/overview.jd
index 85599f76600d..e969f5d299ec 100644
--- a/docs/html/sdk/ndk/overview.jd
+++ b/docs/html/sdk/ndk/overview.jd
@@ -46,7 +46,7 @@ page.title=What is the NDK?
<li>Documentation, samples, and tutorials</li>
</ul>
- <p>The latest release of the NDK supports these ARM instruction sets:</p>
+ <p>The latest release of the NDK supports the following instruction sets:</p>
<ul>
<li>ARMv5TE (including Thumb-1 instructions)</li>
diff --git a/docs/html/training/improving-layouts/optimizing-layout.jd b/docs/html/training/improving-layouts/optimizing-layout.jd
index 65c8af798fd1..0eaf199b132e 100644
--- a/docs/html/training/improving-layouts/optimizing-layout.jd
+++ b/docs/html/training/improving-layouts/optimizing-layout.jd
@@ -18,7 +18,7 @@ next.link=reusing-layouts.html
<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 @@ is inflated repeatedly, such as when used in a {@link android.widget.ListView} o
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 @@ layout.</p>
<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 @@ example of how each layout has appropriate uses and you should carefully conside
layout weight is necessary.</p>
-<h2 id="Layoutopt">Use Layoutopt</h2>
+<h2 id="Lint">Use Lint</h2>
+
+<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>
+
+<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>
+
+<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>
+
+<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>
+
+
-<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>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>
-<pre class="no-pretty-print classic">
-$ layoutopt samples/
-samples/compound.xml
- 7:23 The root-level &lt;FrameLayout/&gt; can be replaced with &lt;merge/&gt;
- 11:21 This LinearLayout layout or its FrameLayout parent is useless
-samples/simple.xml
- 7:7 The root-level &lt;FrameLayout/&gt; can be replaced with &lt;merge/&gt;
-</pre>
-<p>After you apply the suggested layout optimizations, run Hierarchy Viewer again to inspect the
-performance changes.</p>
diff --git a/graphics/java/android/graphics/Rect.java b/graphics/java/android/graphics/Rect.java
index 0ada1fbdcd8d..ec911b06be75 100644
--- a/graphics/java/android/graphics/Rect.java
+++ b/graphics/java/android/graphics/Rect.java
@@ -352,8 +352,7 @@ public final class Rect implements Parcelable {
// 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 @@ public final class Rect implements Parcelable {
* 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 @@ public final class Rect implements Parcelable {
* 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 @@ public final class Rect implements Parcelable {
* 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 @@ public final class Rect implements Parcelable {
* 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 final class Rect implements Parcelable {
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/java/android/renderscript/Allocation.java b/graphics/java/android/renderscript/Allocation.java
index 37a270e9c10d..6539ff374724 100644
--- a/graphics/java/android/renderscript/Allocation.java
+++ b/graphics/java/android/renderscript/Allocation.java
@@ -534,6 +534,7 @@ public class Allocation extends BaseObj {
* @param fp
*/
public void setFromFieldPacker(int xoff, FieldPacker fp) {
+ mRS.validate();
int eSize = mType.mElement.getSizeBytes();
final byte[] data = fp.getData();
@@ -554,6 +555,7 @@ public class Allocation extends BaseObj {
* @param fp
*/
public void setFromFieldPacker(int xoff, int component_number, FieldPacker fp) {
+ mRS.validate();
if (component_number >= mType.mElement.mElements.length) {
throw new RSIllegalArgumentException("Component_number " + component_number + " out of range.");
}
diff --git a/include/media/stagefright/ACodec.h b/include/media/stagefright/ACodec.h
index 3963d9cf5909..70799a638b8a 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 @@ struct ACodec : public AHierarchicalStateMachine {
kWhatFlushCompleted = 'fcom',
kWhatOutputFormatChanged = 'outC',
kWhatError = 'erro',
+ kWhatComponentAllocated = 'cAll',
+ kWhatComponentConfigured = 'cCon',
+ kWhatBuffersAllocated = 'allc',
};
ACodec();
@@ -47,6 +51,10 @@ struct ACodec : public AHierarchicalStateMachine {
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 @@ private:
kWhatFlush = 'flus',
kWhatResume = 'resm',
kWhatDrainDeferredMessages = 'drai',
+ kWhatAllocateComponent = 'allo',
+ kWhatConfigureComponent = 'conf',
+ kWhatStart = 'star',
};
enum {
@@ -118,6 +129,7 @@ private:
List<sp<AMessage> > mDeferredQueue;
bool mSentFormat;
+ bool mIsEncoder;
status_t allocateBuffersOnPort(OMX_U32 portIndex);
status_t freeBuffersOnPort(OMX_U32 portIndex);
@@ -132,8 +144,8 @@ private:
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 @@ private:
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 @@ private:
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 000000000000..8c11c9c67f13
--- /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 21d00b8c531b..dd3bf2876999 100644
--- a/include/media/stagefright/MediaErrors.h
+++ b/include/media/stagefright/MediaErrors.h
@@ -40,6 +40,7 @@ enum {
// 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 f323cbc1935c..97cc0cebda3e 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 000000000000..96efdff9eb35
--- /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 7ec54aa213c4..e5416e4fc9d7 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 @@ struct AMessage : public RefBase {
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 @@ struct AMessage : public RefBase {
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 @@ struct AMessage : public RefBase {
AString debugString(int32_t indent = 0) const;
-protected:
- virtual ~AMessage();
-
-private:
enum Type {
kTypeInt32,
kTypeInt64,
@@ -105,8 +104,16 @@ private:
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 @@ private:
};
enum {
- kMaxNumItems = 16
+ kMaxNumItems = 32
};
Item mItems[kMaxNumItems];
size_t mNumItems;
@@ -140,6 +147,9 @@ private:
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/utils/KeyedVector.h b/include/utils/KeyedVector.h
index fcc3bcfa00a1..85535bd61e97 100644
--- a/include/utils/KeyedVector.h
+++ b/include/utils/KeyedVector.h
@@ -122,7 +122,7 @@ ssize_t KeyedVector<KEY,VALUE>::indexOfKey(const KEY& key) const {
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 @@ const KEY& KeyedVector<KEY,VALUE>::keyAt(size_t index) const {
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 @@ DefaultKeyedVector<KEY,VALUE>::DefaultKeyedVector(const VALUE& defValue)
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/hwui/Debug.h b/libs/hwui/Debug.h
index 16a3d73a121f..55a860ebc69b 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 90a714526623..4a299c6eb47a 100644
--- a/libs/hwui/DisplayListRenderer.h
+++ b/libs/hwui/DisplayListRenderer.h
@@ -462,6 +462,7 @@ private:
SkPath* pathCopy = mPathMap.valueFor(path);
if (pathCopy == NULL || pathCopy->getGenerationID() != path->getGenerationID()) {
pathCopy = new SkPath(*path);
+ pathCopy->setSourcePath(path);
// replaceValueFor() performs an add if the entry doesn't exist
mPathMap.replaceValueFor(path, pathCopy);
mPaths.add(pathCopy);
diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp
index e09c24381d9d..e363b73498df 100644
--- a/libs/hwui/PathCache.cpp
+++ b/libs/hwui/PathCache.cpp
@@ -83,6 +83,11 @@ void PathCache::clearGarbage() {
}
PathTexture* PathCache::get(SkPath* path, SkPaint* paint) {
+ const SkPath* sourcePath = path->getSourcePath();
+ if (sourcePath && sourcePath->getGenerationID() == path->getGenerationID()) {
+ path = const_cast<SkPath*>(sourcePath);
+ }
+
PathCacheEntry entry(path, paint);
PathTexture* texture = mCache.get(entry);
diff --git a/libs/rs/Allocation.cpp b/libs/rs/Allocation.cpp
new file mode 100644
index 000000000000..46df171822bf
--- /dev/null
+++ b/libs/rs/Allocation.cpp
@@ -0,0 +1,469 @@
+/*
+ * Copyright (C) 2008-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 <utils/Log.h>
+#include <malloc.h>
+
+#include "RenderScript.h"
+#include "Element.h"
+#include "Type.h"
+#include "Allocation.h"
+
+
+void * Allocation::getIDSafe() const {
+ //if (mAdaptedAllocation != NULL) {
+ //return mAdaptedAllocation.getID();
+ //}
+ return getID();
+}
+
+void Allocation::updateCacheInfo(const Type *t) {
+ mCurrentDimX = t->getX();
+ mCurrentDimY = t->getY();
+ mCurrentDimZ = t->getZ();
+ mCurrentCount = mCurrentDimX;
+ if (mCurrentDimY > 1) {
+ mCurrentCount *= mCurrentDimY;
+ }
+ if (mCurrentDimZ > 1) {
+ mCurrentCount *= mCurrentDimZ;
+ }
+}
+
+Allocation::Allocation(void *id, RenderScript *rs, const Type *t, uint32_t usage) : BaseObj(id, rs) {
+ if ((usage & ~(RS_ALLOCATION_USAGE_SCRIPT |
+ RS_ALLOCATION_USAGE_GRAPHICS_TEXTURE |
+ RS_ALLOCATION_USAGE_GRAPHICS_VERTEX |
+ RS_ALLOCATION_USAGE_GRAPHICS_CONSTANTS |
+ RS_ALLOCATION_USAGE_GRAPHICS_RENDER_TARGET |
+ RS_ALLOCATION_USAGE_GRAPHICS_SURFACE_TEXTURE_INPUT_OPAQUE |
+ RS_ALLOCATION_USAGE_IO_INPUT |
+ RS_ALLOCATION_USAGE_IO_OUTPUT)) != 0) {
+ ALOGE("Unknown usage specified.");
+ }
+
+ if ((usage & (RS_ALLOCATION_USAGE_GRAPHICS_SURFACE_TEXTURE_INPUT_OPAQUE |
+ RS_ALLOCATION_USAGE_IO_INPUT)) != 0) {
+ mWriteAllowed = false;
+ if ((usage & ~(RS_ALLOCATION_USAGE_GRAPHICS_SURFACE_TEXTURE_INPUT_OPAQUE |
+ RS_ALLOCATION_USAGE_IO_INPUT |
+ RS_ALLOCATION_USAGE_GRAPHICS_TEXTURE |
+ RS_ALLOCATION_USAGE_SCRIPT)) != 0) {
+ ALOGE("Invalid usage combination.");
+ }
+ }
+
+ mType = t;
+ mUsage = usage;
+
+ if (t != NULL) {
+ updateCacheInfo(t);
+ }
+}
+
+void Allocation::validateIsInt32() {
+ RsDataType dt = mType->getElement()->getDataType();
+ if ((dt == RS_TYPE_SIGNED_32) || (dt == RS_TYPE_UNSIGNED_32)) {
+ return;
+ }
+ ALOGE("32 bit integer source does not match allocation type %i", dt);
+}
+
+void Allocation::validateIsInt16() {
+ RsDataType dt = mType->getElement()->getDataType();
+ if ((dt == RS_TYPE_SIGNED_16) || (dt == RS_TYPE_UNSIGNED_16)) {
+ return;
+ }
+ ALOGE("16 bit integer source does not match allocation type %i", dt);
+}
+
+void Allocation::validateIsInt8() {
+ RsDataType dt = mType->getElement()->getDataType();
+ if ((dt == RS_TYPE_SIGNED_8) || (dt == RS_TYPE_UNSIGNED_8)) {
+ return;
+ }
+ ALOGE("8 bit integer source does not match allocation type %i", dt);
+}
+
+void Allocation::validateIsFloat32() {
+ RsDataType dt = mType->getElement()->getDataType();
+ if (dt == RS_TYPE_FLOAT_32) {
+ return;
+ }
+ ALOGE("32 bit float source does not match allocation type %i", dt);
+}
+
+void Allocation::validateIsObject() {
+ RsDataType dt = mType->getElement()->getDataType();
+ if ((dt == RS_TYPE_ELEMENT) ||
+ (dt == RS_TYPE_TYPE) ||
+ (dt == RS_TYPE_ALLOCATION) ||
+ (dt == RS_TYPE_SAMPLER) ||
+ (dt == RS_TYPE_SCRIPT) ||
+ (dt == RS_TYPE_MESH) ||
+ (dt == RS_TYPE_PROGRAM_FRAGMENT) ||
+ (dt == RS_TYPE_PROGRAM_VERTEX) ||
+ (dt == RS_TYPE_PROGRAM_RASTER) ||
+ (dt == RS_TYPE_PROGRAM_STORE)) {
+ return;
+ }
+ ALOGE("Object source does not match allocation type %i", dt);
+}
+
+void Allocation::updateFromNative() {
+ BaseObj::updateFromNative();
+
+ const void *typeID = rsaAllocationGetType(mRS->mContext, getID());
+ if(typeID != NULL) {
+ const Type *old = mType;
+ Type *t = new Type((void *)typeID, mRS);
+ t->updateFromNative();
+ updateCacheInfo(t);
+ mType = t;
+ delete old;
+ }
+}
+
+void Allocation::syncAll(RsAllocationUsageType srcLocation) {
+ switch (srcLocation) {
+ case RS_ALLOCATION_USAGE_SCRIPT:
+ case RS_ALLOCATION_USAGE_GRAPHICS_CONSTANTS:
+ case RS_ALLOCATION_USAGE_GRAPHICS_TEXTURE:
+ case RS_ALLOCATION_USAGE_GRAPHICS_VERTEX:
+ break;
+ default:
+ ALOGE("Source must be exactly one usage type.");
+ }
+ rsAllocationSyncAll(mRS->mContext, getIDSafe(), srcLocation);
+}
+
+void Allocation::ioSendOutput() {
+ if ((mUsage & RS_ALLOCATION_USAGE_IO_OUTPUT) == 0) {
+ ALOGE("Can only send buffer if IO_OUTPUT usage specified.");
+ }
+ rsAllocationIoSend(mRS->mContext, getID());
+}
+
+void Allocation::ioGetInput() {
+ if ((mUsage & RS_ALLOCATION_USAGE_IO_INPUT) == 0) {
+ ALOGE("Can only send buffer if IO_OUTPUT usage specified.");
+ }
+ rsAllocationIoReceive(mRS->mContext, getID());
+}
+
+/*
+void copyFrom(BaseObj[] d) {
+ mRS.validate();
+ validateIsObject();
+ if (d.length != mCurrentCount) {
+ ALOGE("Array size mismatch, allocation sizeX = " +
+ mCurrentCount + ", array length = " + d.length);
+ }
+ int i[] = new int[d.length];
+ for (int ct=0; ct < d.length; ct++) {
+ i[ct] = d[ct].getID();
+ }
+ copy1DRangeFromUnchecked(0, mCurrentCount, i);
+}
+*/
+
+
+/*
+void Allocation::setFromFieldPacker(int xoff, FieldPacker fp) {
+ mRS.validate();
+ int eSize = mType.mElement.getSizeBytes();
+ final byte[] data = fp.getData();
+
+ int count = data.length / eSize;
+ if ((eSize * count) != data.length) {
+ ALOGE("Field packer length " + data.length +
+ " not divisible by element size " + eSize + ".");
+ }
+ copy1DRangeFromUnchecked(xoff, count, data);
+}
+
+void setFromFieldPacker(int xoff, int component_number, FieldPacker fp) {
+ mRS.validate();
+ if (component_number >= mType.mElement.mElements.length) {
+ ALOGE("Component_number " + component_number + " out of range.");
+ }
+ if(xoff < 0) {
+ ALOGE("Offset must be >= 0.");
+ }
+
+ final byte[] data = fp.getData();
+ int eSize = mType.mElement.mElements[component_number].getSizeBytes();
+ eSize *= mType.mElement.mArraySizes[component_number];
+
+ if (data.length != eSize) {
+ ALOGE("Field packer sizelength " + data.length +
+ " does not match component size " + eSize + ".");
+ }
+
+ mRS.nAllocationElementData1D(getIDSafe(), xoff, mSelectedLOD,
+ component_number, data, data.length);
+}
+*/
+
+void Allocation::generateMipmaps() {
+ rsAllocationGenerateMipmaps(mRS->mContext, getID());
+}
+
+void Allocation::copy1DRangeFromUnchecked(uint32_t off, size_t count, const void *data, size_t dataLen) {
+ if(count < 1) {
+ ALOGE("Count must be >= 1.");
+ return;
+ }
+ if((off + count) > mCurrentCount) {
+ ALOGE("Overflow, Available count %zu, got %zu at offset %zu.", mCurrentCount, count, off);
+ return;
+ }
+ if((count * mType->getElement()->getSizeBytes()) > dataLen) {
+ ALOGE("Array too small for allocation type.");
+ return;
+ }
+
+ rsAllocation1DData(mRS->mContext, getIDSafe(), off, mSelectedLOD, count, data, dataLen);
+}
+
+void Allocation::copy1DRangeFrom(uint32_t off, size_t count, const int32_t *d, size_t dataLen) {
+ validateIsInt32();
+ copy1DRangeFromUnchecked(off, count, d, dataLen);
+}
+
+void Allocation::copy1DRangeFrom(uint32_t off, size_t count, const int16_t *d, size_t dataLen) {
+ validateIsInt16();
+ copy1DRangeFromUnchecked(off, count, d, dataLen);
+}
+
+void Allocation::copy1DRangeFrom(uint32_t off, size_t count, const int8_t *d, size_t dataLen) {
+ validateIsInt8();
+ copy1DRangeFromUnchecked(off, count, d, dataLen);
+}
+
+void Allocation::copy1DRangeFrom(uint32_t off, size_t count, const float *d, size_t dataLen) {
+ validateIsFloat32();
+ copy1DRangeFromUnchecked(off, count, d, dataLen);
+}
+
+void Allocation::copy1DRangeFrom(uint32_t off, size_t count, const Allocation *data, uint32_t dataOff) {
+ rsAllocationCopy2DRange(mRS->mContext, getIDSafe(), off, 0,
+ mSelectedLOD, mSelectedFace,
+ count, 1, data->getIDSafe(), dataOff, 0,
+ data->mSelectedLOD, data->mSelectedFace);
+}
+
+void Allocation::validate2DRange(uint32_t xoff, uint32_t yoff, uint32_t w, uint32_t h) {
+ if (mAdaptedAllocation != NULL) {
+
+ } else {
+ if (((xoff + w) > mCurrentDimX) || ((yoff + h) > mCurrentDimY)) {
+ ALOGE("Updated region larger than allocation.");
+ }
+ }
+}
+
+void Allocation::copy2DRangeFrom(uint32_t xoff, uint32_t yoff, uint32_t w, uint32_t h,
+ const int8_t *data, size_t dataLen) {
+ validate2DRange(xoff, yoff, w, h);
+ rsAllocation2DData(mRS->mContext, getIDSafe(), xoff, yoff, mSelectedLOD, mSelectedFace,
+ w, h, data, dataLen);
+}
+
+void Allocation::copy2DRangeFrom(uint32_t xoff, uint32_t yoff, uint32_t w, uint32_t h,
+ const int16_t *data, size_t dataLen) {
+ validate2DRange(xoff, yoff, w, h);
+ rsAllocation2DData(mRS->mContext, getIDSafe(), xoff, yoff, mSelectedLOD, mSelectedFace,
+ w, h, data, dataLen);
+}
+
+void Allocation::copy2DRangeFrom(uint32_t xoff, uint32_t yoff, uint32_t w, uint32_t h,
+ const int32_t *data, size_t dataLen) {
+ validate2DRange(xoff, yoff, w, h);
+ rsAllocation2DData(mRS->mContext, getIDSafe(), xoff, yoff, mSelectedLOD, mSelectedFace,
+ w, h, data, dataLen);
+}
+
+void Allocation::copy2DRangeFrom(uint32_t xoff, uint32_t yoff, uint32_t w, uint32_t h,
+ const float *data, size_t dataLen) {
+ validate2DRange(xoff, yoff, w, h);
+ rsAllocation2DData(mRS->mContext, getIDSafe(), xoff, yoff, mSelectedLOD, mSelectedFace,
+ w, h, data, dataLen);
+}
+
+void Allocation::copy2DRangeFrom(uint32_t xoff, uint32_t yoff, uint32_t w, uint32_t h,
+ const Allocation *data, size_t dataLen,
+ uint32_t dataXoff, uint32_t dataYoff) {
+ validate2DRange(xoff, yoff, w, h);
+ rsAllocationCopy2DRange(mRS->mContext, getIDSafe(), xoff, yoff,
+ mSelectedLOD, mSelectedFace,
+ w, h, data->getIDSafe(), dataXoff, dataYoff,
+ data->mSelectedLOD, data->mSelectedFace);
+}
+
+/*
+void copyTo(byte[] d) {
+ validateIsInt8();
+ mRS.validate();
+ mRS.nAllocationRead(getID(), d);
+}
+
+void copyTo(short[] d) {
+ validateIsInt16();
+ mRS.validate();
+ mRS.nAllocationRead(getID(), d);
+}
+
+void copyTo(int[] d) {
+ validateIsInt32();
+ mRS.validate();
+ mRS.nAllocationRead(getID(), d);
+}
+
+void copyTo(float[] d) {
+ validateIsFloat32();
+ mRS.validate();
+ mRS.nAllocationRead(getID(), d);
+}
+
+void resize(int dimX) {
+ if ((mType.getY() > 0)|| (mType.getZ() > 0) || mType.hasFaces() || mType.hasMipmaps()) {
+ throw new RSInvalidStateException("Resize only support for 1D allocations at this time.");
+ }
+ mRS.nAllocationResize1D(getID(), dimX);
+ mRS.finish(); // Necessary because resize is fifoed and update is async.
+
+ int typeID = mRS.nAllocationGetType(getID());
+ mType = new Type(typeID, mRS);
+ mType.updateFromNative();
+ updateCacheInfo(mType);
+}
+
+void resize(int dimX, int dimY) {
+ if ((mType.getZ() > 0) || mType.hasFaces() || mType.hasMipmaps()) {
+ throw new RSInvalidStateException(
+ "Resize only support for 2D allocations at this time.");
+ }
+ if (mType.getY() == 0) {
+ throw new RSInvalidStateException(
+ "Resize only support for 2D allocations at this time.");
+ }
+ mRS.nAllocationResize2D(getID(), dimX, dimY);
+ mRS.finish(); // Necessary because resize is fifoed and update is async.
+
+ int typeID = mRS.nAllocationGetType(getID());
+ mType = new Type(typeID, mRS);
+ mType.updateFromNative();
+ updateCacheInfo(mType);
+}
+*/
+
+
+Allocation *Allocation::createTyped(RenderScript *rs, const Type *type,
+ RsAllocationMipmapControl mips, uint32_t usage) {
+ void *id = rsAllocationCreateTyped(rs->mContext, type->getID(), mips, usage, 0);
+ if (id == 0) {
+ ALOGE("Allocation creation failed.");
+ return NULL;
+ }
+ return new Allocation(id, rs, type, usage);
+}
+
+Allocation *Allocation::createTyped(RenderScript *rs, const Type *type,
+ RsAllocationMipmapControl mips, uint32_t usage, void *pointer) {
+ void *id = rsAllocationCreateTyped(rs->mContext, type->getID(), mips, usage, (uint32_t)pointer);
+ if (id == 0) {
+ ALOGE("Allocation creation failed.");
+ }
+ return new Allocation(id, rs, type, usage);
+}
+
+Allocation *Allocation::createTyped(RenderScript *rs, const Type *type, uint32_t usage) {
+ return createTyped(rs, type, RS_ALLOCATION_MIPMAP_NONE, usage);
+}
+
+Allocation *Allocation::createSized(RenderScript *rs, const Element *e, size_t count, uint32_t usage) {
+ Type::Builder b(rs, e);
+ b.setX(count);
+ const Type *t = b.create();
+
+ void *id = rsAllocationCreateTyped(rs->mContext, t->getID(), RS_ALLOCATION_MIPMAP_NONE, usage, 0);
+ if (id == 0) {
+ ALOGE("Allocation creation failed.");
+ }
+ return new Allocation(id, rs, t, usage);
+}
+
+
+/*
+SurfaceTexture getSurfaceTexture() {
+ if ((mUsage & USAGE_GRAPHICS_SURFACE_TEXTURE_INPUT_OPAQUE) == 0) {
+ throw new RSInvalidStateException("Allocation is not a surface texture.");
+ }
+
+ int id = mRS.nAllocationGetSurfaceTextureID(getID());
+ return new SurfaceTexture(id);
+
+}
+
+void setSurfaceTexture(SurfaceTexture sur) {
+ if ((mUsage & USAGE_IO_OUTPUT) == 0) {
+ throw new RSInvalidStateException("Allocation is not USAGE_IO_OUTPUT.");
+ }
+
+ mRS.validate();
+ mRS.nAllocationSetSurfaceTexture(getID(), sur);
+}
+
+
+static Allocation createFromBitmapResource(RenderScript rs,
+ Resources res,
+ int id,
+ MipmapControl mips,
+ int usage) {
+
+ rs.validate();
+ Bitmap b = BitmapFactory.decodeResource(res, id);
+ Allocation alloc = createFromBitmap(rs, b, mips, usage);
+ b.recycle();
+ return alloc;
+}
+
+static Allocation createFromBitmapResource(RenderScript rs,
+ Resources res,
+ int id) {
+ return createFromBitmapResource(rs, res, id,
+ MipmapControl.MIPMAP_NONE,
+ USAGE_GRAPHICS_TEXTURE);
+}
+
+static Allocation createFromString(RenderScript rs,
+ String str,
+ int usage) {
+ rs.validate();
+ byte[] allocArray = NULL;
+ try {
+ allocArray = str.getBytes("UTF-8");
+ Allocation alloc = Allocation.createSized(rs, Element.U8(rs), allocArray.length, usage);
+ alloc.copyFrom(allocArray);
+ return alloc;
+ }
+ catch (Exception e) {
+ throw new RSRuntimeException("Could not convert string to utf-8.");
+ }
+}
+*/
+
diff --git a/libs/rs/Allocation.h b/libs/rs/Allocation.h
new file mode 100644
index 000000000000..c9e00a4b2446
--- /dev/null
+++ b/libs/rs/Allocation.h
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2008-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_ALLOCATION_H__
+#define __ANDROID_ALLOCATION_H__
+
+#include <pthread.h>
+#include <rs.h>
+
+#include "RenderScript.h"
+#include "Type.h"
+#include "Element.h"
+
+class Allocation : public BaseObj {
+protected:
+ const Type *mType;
+ uint32_t mUsage;
+ Allocation *mAdaptedAllocation;
+
+ bool mConstrainedLOD;
+ bool mConstrainedFace;
+ bool mConstrainedY;
+ bool mConstrainedZ;
+ bool mReadAllowed;
+ bool mWriteAllowed;
+ uint32_t mSelectedY;
+ uint32_t mSelectedZ;
+ uint32_t mSelectedLOD;
+ RsAllocationCubemapFace mSelectedFace;
+
+ uint32_t mCurrentDimX;
+ uint32_t mCurrentDimY;
+ uint32_t mCurrentDimZ;
+ uint32_t mCurrentCount;
+
+
+ void * getIDSafe() const;
+ void updateCacheInfo(const Type *t);
+
+ Allocation(void *id, RenderScript *rs, const Type *t, uint32_t usage);
+
+ void validateIsInt32();
+ void validateIsInt16();
+ void validateIsInt8();
+ void validateIsFloat32();
+ void validateIsObject();
+
+ virtual void updateFromNative();
+
+ void validate2DRange(uint32_t xoff, uint32_t yoff, uint32_t w, uint32_t h);
+
+public:
+ const Type * getType() {
+ return mType;
+ }
+
+ void syncAll(RsAllocationUsageType srcLocation);
+ void ioSendOutput();
+ void ioGetInput();
+
+ //void copyFrom(BaseObj[] d);
+ //void copyFromUnchecked(int[] d);
+ //void copyFromUnchecked(short[] d);
+ //void copyFromUnchecked(byte[] d);
+ //void copyFromUnchecked(float[] d);
+ //void copyFrom(int[] d);
+ //void copyFrom(short[] d);
+ //void copyFrom(byte[] d);
+ //void copyFrom(float[] d);
+ //void setFromFieldPacker(int xoff, FieldPacker fp);
+ //void setFromFieldPacker(int xoff, int component_number, FieldPacker fp);
+ void generateMipmaps();
+ void copy1DRangeFromUnchecked(uint32_t off, size_t count, const void *data, size_t dataLen);
+ void copy1DRangeFrom(uint32_t off, size_t count, const int32_t* d, size_t dataLen);
+ void copy1DRangeFrom(uint32_t off, size_t count, const int16_t* d, size_t dataLen);
+ void copy1DRangeFrom(uint32_t off, size_t count, const int8_t* d, size_t dataLen);
+ void copy1DRangeFrom(uint32_t off, size_t count, const float* d, size_t dataLen);
+ void copy1DRangeFrom(uint32_t off, size_t count, const Allocation *data, uint32_t dataOff);
+
+ void copy2DRangeFrom(uint32_t xoff, uint32_t yoff, uint32_t w, uint32_t h,
+ const int32_t *data, size_t dataLen);
+ void copy2DRangeFrom(uint32_t xoff, uint32_t yoff, uint32_t w, uint32_t h,
+ const int16_t *data, size_t dataLen);
+ void copy2DRangeFrom(uint32_t xoff, uint32_t yoff, uint32_t w, uint32_t h,
+ const int8_t *data, size_t dataLen);
+ void copy2DRangeFrom(uint32_t xoff, uint32_t yoff, uint32_t w, uint32_t h,
+ const float *data, size_t dataLen);
+ void copy2DRangeFrom(uint32_t xoff, uint32_t yoff, uint32_t w, uint32_t h,
+ const Allocation *data, size_t dataLen,
+ uint32_t dataXoff, uint32_t dataYoff);
+
+ //void copyTo(byte[] d);
+ //void copyTo(short[] d);
+ //void copyTo(int[] d);
+ //void copyTo(float[] d);
+ void resize(int dimX);
+ void resize(int dimX, int dimY);
+
+ static Allocation *createTyped(RenderScript *rs, const Type *type,
+ RsAllocationMipmapControl mips, uint32_t usage);
+ static Allocation *createTyped(RenderScript *rs, const Type *type,
+ RsAllocationMipmapControl mips, uint32_t usage, void * pointer);
+
+ static Allocation *createTyped(RenderScript *rs, const Type *type,
+ uint32_t usage = RS_ALLOCATION_USAGE_SCRIPT);
+ static Allocation *createSized(RenderScript *rs, const Element *e, size_t count,
+ uint32_t usage = RS_ALLOCATION_USAGE_SCRIPT);
+ //SurfaceTexture *getSurfaceTexture();
+ //void setSurfaceTexture(SurfaceTexture *sur);
+
+};
+
+#endif
diff --git a/libs/rs/Android.mk b/libs/rs/Android.mk
index 9c5d06b7333f..45ed453e60a7 100644
--- a/libs/rs/Android.mk
+++ b/libs/rs/Android.mk
@@ -127,7 +127,14 @@ LOCAL_SRC_FILES:= \
driver/rsdSampler.cpp \
driver/rsdShader.cpp \
driver/rsdShaderCache.cpp \
- driver/rsdVertexArray.cpp
+ driver/rsdVertexArray.cpp \
+ RenderScript.cpp \
+ BaseObj.cpp \
+ Element.cpp \
+ Type.cpp \
+ Allocation.cpp \
+ Script.cpp \
+ ScriptC.cpp
LOCAL_SHARED_LIBRARIES += libz libcutils libutils libEGL libGLESv1_CM libGLESv2 libui libbcc libbcinfo libgui
diff --git a/libs/rs/BaseObj.cpp b/libs/rs/BaseObj.cpp
new file mode 100644
index 000000000000..66e6fac711c2
--- /dev/null
+++ b/libs/rs/BaseObj.cpp
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2008-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 <rs.h>
+
+#include "RenderScript.h"
+#include "BaseObj.h"
+
+void * BaseObj::getID() const {
+ if (mID == NULL) {
+ ALOGE("Internal error: Object id 0.");
+ }
+ return mID;
+}
+
+void * BaseObj::getObjID(const BaseObj *o) {
+ return o == NULL ? NULL : o->getID();
+}
+
+
+BaseObj::BaseObj(void *id, RenderScript *rs) {
+ mRS = rs;
+ mID = id;
+}
+
+void BaseObj::checkValid() {
+ if (mID == 0) {
+ ALOGE("Invalid object.");
+ }
+}
+
+BaseObj::~BaseObj() {
+ rsObjDestroy(mRS->mContext, mID);
+ mRS = NULL;
+ mID = NULL;
+}
+
+void BaseObj::updateFromNative() {
+ const char *name = NULL;
+ rsaGetName(mRS, mID, &name);
+ mName = name;
+}
+
+bool BaseObj::equals(const BaseObj *obj) {
+ // Early-out check to see if both BaseObjs are actually the same
+ if (this == obj)
+ return true;
+ return mID == obj->mID;
+}
+
+
+
diff --git a/libs/rs/BaseObj.h b/libs/rs/BaseObj.h
new file mode 100644
index 000000000000..79761b1aa902
--- /dev/null
+++ b/libs/rs/BaseObj.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2008-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_BASE_OBJ_H__
+#define __ANDROID_BASE_OBJ_H__
+
+
+#include <pthread.h>
+#include <rs.h>
+
+#include "RenderScript.h"
+
+class BaseObj {
+protected:
+ friend class Element;
+ friend class Type;
+ friend class Allocation;
+ friend class Script;
+ friend class ScriptC;
+
+ void *mID;
+ RenderScript *mRS;
+ android::String8 mName;
+
+ void * getID() const;
+
+ BaseObj(void *id, RenderScript *rs);
+ void checkValid();
+
+ static void * getObjID(const BaseObj *o);
+
+public:
+
+ virtual ~BaseObj();
+ virtual void updateFromNative();
+ virtual bool equals(const BaseObj *obj);
+};
+
+#endif
diff --git a/libs/rs/Element.cpp b/libs/rs/Element.cpp
new file mode 100644
index 000000000000..d193892fcf14
--- /dev/null
+++ b/libs/rs/Element.cpp
@@ -0,0 +1,426 @@
+/*
+ * Copyright (C) 2008-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 <utils/Log.h>
+#include <malloc.h>
+#include <string.h>
+
+#include "RenderScript.h"
+#include "Element.h"
+
+
+const Element * Element::getSubElement(uint32_t index) {
+ if (!mVisibleElementMap.size()) {
+ mRS->throwError("Element contains no sub-elements");
+ }
+ if (index >= mVisibleElementMap.size()) {
+ mRS->throwError("Illegal sub-element index");
+ }
+ return mElements[mVisibleElementMap[index]];
+}
+
+const char * Element::getSubElementName(uint32_t index) {
+ if (!mVisibleElementMap.size()) {
+ mRS->throwError("Element contains no sub-elements");
+ }
+ if (index >= mVisibleElementMap.size()) {
+ mRS->throwError("Illegal sub-element index");
+ }
+ return mElementNames[mVisibleElementMap[index]];
+}
+
+size_t Element::getSubElementArraySize(uint32_t index) {
+ if (!mVisibleElementMap.size()) {
+ mRS->throwError("Element contains no sub-elements");
+ }
+ if (index >= mVisibleElementMap.size()) {
+ mRS->throwError("Illegal sub-element index");
+ }
+ return mArraySizes[mVisibleElementMap[index]];
+}
+
+uint32_t Element::getSubElementOffsetBytes(uint32_t index) {
+ if (mVisibleElementMap.size()) {
+ mRS->throwError("Element contains no sub-elements");
+ }
+ if (index >= mVisibleElementMap.size()) {
+ mRS->throwError("Illegal sub-element index");
+ }
+ return mOffsetInBytes[mVisibleElementMap[index]];
+}
+
+
+#define CREATE_USER(N, T) const Element * Element::N(RenderScript *rs) { \
+ return createUser(rs, RS_TYPE_##T); \
+}
+CREATE_USER(BOOLEAN, BOOLEAN);
+CREATE_USER(U8, UNSIGNED_8);
+CREATE_USER(I8, SIGNED_8);
+CREATE_USER(U16, UNSIGNED_16);
+CREATE_USER(I16, SIGNED_16);
+CREATE_USER(U32, UNSIGNED_32);
+CREATE_USER(I32, SIGNED_32);
+CREATE_USER(U64, UNSIGNED_64);
+CREATE_USER(I64, SIGNED_64);
+CREATE_USER(F32, FLOAT_32);
+CREATE_USER(F64, FLOAT_64);
+CREATE_USER(ELEMENT, ELEMENT);
+CREATE_USER(TYPE, TYPE);
+CREATE_USER(ALLOCATION, ALLOCATION);
+CREATE_USER(SAMPLER, SAMPLER);
+CREATE_USER(SCRIPT, SCRIPT);
+CREATE_USER(MESH, MESH);
+CREATE_USER(PROGRAM_FRAGMENT, PROGRAM_FRAGMENT);
+CREATE_USER(PROGRAM_VERTEX, PROGRAM_VERTEX);
+CREATE_USER(PROGRAM_RASTER, PROGRAM_RASTER);
+CREATE_USER(PROGRAM_STORE, PROGRAM_STORE);
+CREATE_USER(MATRIX_4X4, MATRIX_4X4);
+CREATE_USER(MATRIX_3X3, MATRIX_3X3);
+CREATE_USER(MATRIX_2X2, MATRIX_2X2);
+
+#define CREATE_PIXEL(N, T, K) const Element * Element::N(RenderScript *rs) { \
+ return createPixel(rs, RS_TYPE_##T, RS_KIND_##K); \
+}
+CREATE_PIXEL(A_8, UNSIGNED_8, PIXEL_A);
+CREATE_PIXEL(RGB_565, UNSIGNED_5_6_5, PIXEL_RGB);
+CREATE_PIXEL(RGB_888, UNSIGNED_8, PIXEL_RGB);
+CREATE_PIXEL(RGBA_4444, UNSIGNED_4_4_4_4, PIXEL_RGBA);
+CREATE_PIXEL(RGBA_8888, UNSIGNED_8, PIXEL_RGBA);
+
+#define CREATE_VECTOR(N, T) const Element * Element::N##_2(RenderScript *rs) { \
+ return createVector(rs, RS_TYPE_##T, 2); \
+} \
+const Element * Element::N##_3(RenderScript *rs) { \
+ return createVector(rs, RS_TYPE_##T, 3); \
+} \
+const Element * Element::N##_4(RenderScript *rs) { \
+ return createVector(rs, RS_TYPE_##T, 4); \
+}
+CREATE_VECTOR(U8, UNSIGNED_8);
+CREATE_VECTOR(I8, SIGNED_8);
+CREATE_VECTOR(U16, UNSIGNED_16);
+CREATE_VECTOR(I16, SIGNED_16);
+CREATE_VECTOR(U32, UNSIGNED_32);
+CREATE_VECTOR(I32, SIGNED_32);
+CREATE_VECTOR(U64, UNSIGNED_64);
+CREATE_VECTOR(I64, SIGNED_64);
+CREATE_VECTOR(F32, FLOAT_32);
+CREATE_VECTOR(F64, FLOAT_64);
+
+
+void Element::updateVisibleSubElements() {
+ if (!mElements.size()) {
+ return;
+ }
+ mVisibleElementMap.clear();
+
+ int noPaddingFieldCount = 0;
+ size_t fieldCount = mElementNames.size();
+ // Find out how many elements are not padding
+ for (size_t ct = 0; ct < fieldCount; ct ++) {
+ if (mElementNames[ct].string()[0] != '#') {
+ noPaddingFieldCount ++;
+ }
+ }
+
+ // Make a map that points us at non-padding elements
+ for (size_t ct = 0; ct < fieldCount; ct ++) {
+ if (mElementNames[ct].string()[0] != '#') {
+ mVisibleElementMap.push((uint32_t)ct);
+ }
+ }
+}
+
+Element::Element(void *id, RenderScript *rs,
+ android::Vector<const Element *> &elements,
+ android::Vector<android::String8> &elementNames,
+ android::Vector<uint32_t> &arraySizes) : BaseObj(id, rs) {
+ mSizeBytes = 0;
+ mVectorSize = 1;
+ mElements = elements;
+ mArraySizes = arraySizes;
+ mElementNames = elementNames;
+
+ mType = RS_TYPE_NONE;
+ mKind = RS_KIND_USER;
+
+ for (size_t ct = 0; ct < mElements.size(); ct++ ) {
+ mOffsetInBytes.push(mSizeBytes);
+ mSizeBytes += mElements[ct]->mSizeBytes * mArraySizes[ct];
+ }
+ updateVisibleSubElements();
+}
+
+
+static uint32_t GetSizeInBytesForType(RsDataType dt) {
+ switch(dt) {
+ case RS_TYPE_NONE:
+ return 0;
+ case RS_TYPE_SIGNED_8:
+ case RS_TYPE_UNSIGNED_8:
+ case RS_TYPE_BOOLEAN:
+ return 1;
+
+ case RS_TYPE_FLOAT_16:
+ case RS_TYPE_SIGNED_16:
+ case RS_TYPE_UNSIGNED_16:
+ case RS_TYPE_UNSIGNED_5_6_5:
+ case RS_TYPE_UNSIGNED_5_5_5_1:
+ case RS_TYPE_UNSIGNED_4_4_4_4:
+ return 2;
+
+ case RS_TYPE_FLOAT_32:
+ case RS_TYPE_SIGNED_32:
+ case RS_TYPE_UNSIGNED_32:
+ return 4;
+
+ case RS_TYPE_FLOAT_64:
+ case RS_TYPE_SIGNED_64:
+ case RS_TYPE_UNSIGNED_64:
+ return 8;
+
+ case RS_TYPE_MATRIX_4X4:
+ return 16 * 4;
+ case RS_TYPE_MATRIX_3X3:
+ return 9 * 4;
+ case RS_TYPE_MATRIX_2X2:
+ return 4 * 4;
+
+ case RS_TYPE_TYPE:
+ case RS_TYPE_ALLOCATION:
+ case RS_TYPE_SAMPLER:
+ case RS_TYPE_SCRIPT:
+ case RS_TYPE_MESH:
+ case RS_TYPE_PROGRAM_FRAGMENT:
+ case RS_TYPE_PROGRAM_VERTEX:
+ case RS_TYPE_PROGRAM_RASTER:
+ case RS_TYPE_PROGRAM_STORE:
+ return 4;
+
+ default:
+ break;
+ }
+
+ ALOGE("Missing type %i", dt);
+ return 0;
+}
+
+Element::Element(void *id, RenderScript *rs,
+ RsDataType dt, RsDataKind dk, bool norm, uint32_t size) :
+ BaseObj(id, rs)
+{
+ uint32_t tsize = GetSizeInBytesForType(dt);
+ if ((dt != RS_TYPE_UNSIGNED_5_6_5) &&
+ (dt != RS_TYPE_UNSIGNED_4_4_4_4) &&
+ (dt != RS_TYPE_UNSIGNED_5_5_5_1)) {
+ if (size == 3) {
+ mSizeBytes = tsize * 4;
+ } else {
+ mSizeBytes = tsize * size;
+ }
+ } else {
+ mSizeBytes = tsize;
+ }
+ mType = dt;
+ mKind = dk;
+ mNormalized = norm;
+ mVectorSize = size;
+}
+
+Element::~Element() {
+}
+
+ /*
+ Element(int id, RenderScript rs) {
+ super(id, rs);
+ }
+ */
+
+void Element::updateFromNative() {
+ BaseObj::updateFromNative();
+/*
+ // we will pack mType; mKind; mNormalized; mVectorSize; NumSubElements
+ int[] dataBuffer = new int[5];
+ mRS.nElementGetNativeData(getID(), dataBuffer);
+
+ mNormalized = dataBuffer[2] == 1 ? true : false;
+ mVectorSize = dataBuffer[3];
+ mSize = 0;
+ for (DataType dt: DataType.values()) {
+ if(dt.mID == dataBuffer[0]){
+ mType = dt;
+ mSize = mType.mSize * mVectorSize;
+ }
+ }
+ for (DataKind dk: DataKind.values()) {
+ if(dk.mID == dataBuffer[1]){
+ mKind = dk;
+ }
+ }
+
+ int numSubElements = dataBuffer[4];
+ if(numSubElements > 0) {
+ mElements = new Element[numSubElements];
+ mElementNames = new String[numSubElements];
+ mArraySizes = new int[numSubElements];
+ mOffsetInBytes = new int[numSubElements];
+
+ int[] subElementIds = new int[numSubElements];
+ mRS.nElementGetSubElements(getID(), subElementIds, mElementNames, mArraySizes);
+ for(int i = 0; i < numSubElements; i ++) {
+ mElements[i] = new Element(subElementIds[i], mRS);
+ mElements[i].updateFromNative();
+ mOffsetInBytes[i] = mSize;
+ mSize += mElements[i].mSize * mArraySizes[i];
+ }
+ }
+ */
+ updateVisibleSubElements();
+}
+
+const Element * Element::createUser(RenderScript *rs, RsDataType dt) {
+ void * id = rsElementCreate(rs->mContext, dt, RS_KIND_USER, false, 1);
+ return new Element(id, rs, dt, RS_KIND_USER, false, 1);
+}
+
+const Element * Element::createVector(RenderScript *rs, RsDataType dt, uint32_t size) {
+ if (size < 2 || size > 4) {
+ rs->throwError("Vector size out of range 2-4.");
+ }
+ void *id = rsElementCreate(rs->mContext, dt, RS_KIND_USER, false, size);
+ return new Element(id, rs, dt, RS_KIND_USER, false, size);
+}
+
+const Element * Element::createPixel(RenderScript *rs, RsDataType dt, RsDataKind dk) {
+ if (!(dk == RS_KIND_PIXEL_L ||
+ dk == RS_KIND_PIXEL_A ||
+ dk == RS_KIND_PIXEL_LA ||
+ dk == RS_KIND_PIXEL_RGB ||
+ dk == RS_KIND_PIXEL_RGBA ||
+ dk == RS_KIND_PIXEL_DEPTH)) {
+ rs->throwError("Unsupported DataKind");
+ }
+ if (!(dt == RS_TYPE_UNSIGNED_8 ||
+ dt == RS_TYPE_UNSIGNED_16 ||
+ dt == RS_TYPE_UNSIGNED_5_6_5 ||
+ dt == RS_TYPE_UNSIGNED_4_4_4_4 ||
+ dt == RS_TYPE_UNSIGNED_5_5_5_1)) {
+ rs->throwError("Unsupported DataType");
+ }
+ if (dt == RS_TYPE_UNSIGNED_5_6_5 && dk != RS_KIND_PIXEL_RGB) {
+ rs->throwError("Bad kind and type combo");
+ }
+ if (dt == RS_TYPE_UNSIGNED_5_5_5_1 && dk != RS_KIND_PIXEL_RGBA) {
+ rs->throwError("Bad kind and type combo");
+ }
+ if (dt == RS_TYPE_UNSIGNED_4_4_4_4 && dk != RS_KIND_PIXEL_RGBA) {
+ rs->throwError("Bad kind and type combo");
+ }
+ if (dt == RS_TYPE_UNSIGNED_16 && dk != RS_KIND_PIXEL_DEPTH) {
+ rs->throwError("Bad kind and type combo");
+ }
+
+ int size = 1;
+ switch (dk) {
+ case RS_KIND_PIXEL_LA:
+ size = 2;
+ break;
+ case RS_KIND_PIXEL_RGB:
+ size = 3;
+ break;
+ case RS_KIND_PIXEL_RGBA:
+ size = 4;
+ break;
+ case RS_KIND_PIXEL_DEPTH:
+ size = 2;
+ break;
+ default:
+ break;
+ }
+
+ void * id = rsElementCreate(rs->mContext, dt, dk, true, size);
+ return new Element(id, rs, dt, dk, true, size);
+}
+
+bool Element::isCompatible(const Element *e) {
+ // Try strict BaseObj equality to start with.
+ if (this == e) {
+ return true;
+ }
+
+ // Ignore mKind because it is allowed to be different (user vs. pixel).
+ // We also ignore mNormalized because it can be different. The mType
+ // field must be non-null since we require name equivalence for
+ // user-created Elements.
+ return ((mSizeBytes == e->mSizeBytes) &&
+ (mType != NULL) &&
+ (mType == e->mType) &&
+ (mVectorSize == e->mVectorSize));
+}
+
+Element::Builder::Builder(RenderScript *rs) {
+ mRS = rs;
+ mSkipPadding = false;
+}
+
+void Element::Builder::add(const Element *e, android::String8 &name, uint32_t arraySize) {
+ // Skip padding fields after a vector 3 type.
+ if (mSkipPadding) {
+ const char *s1 = "#padding_";
+ const char *s2 = name;
+ size_t len = strlen(s1);
+ if (strlen(s2) >= len) {
+ if (!memcmp(s1, s2, len)) {
+ mSkipPadding = false;
+ return;
+ }
+ }
+ }
+
+ if (e->mVectorSize == 3) {
+ mSkipPadding = true;
+ } else {
+ mSkipPadding = false;
+ }
+
+ mElements.add(e);
+ mElementNames.add(name);
+ mArraySizes.add(arraySize);
+}
+
+const Element * Element::Builder::create() {
+ size_t fieldCount = mElements.size();
+ const char ** nameArray = (const char **)calloc(fieldCount, sizeof(char *));
+ size_t* sizeArray = (size_t*)calloc(fieldCount, sizeof(size_t));
+
+ for (size_t ct = 0; ct < fieldCount; ct++) {
+ nameArray[ct] = mElementNames[ct].string();
+ sizeArray[ct] = mElementNames[ct].length();
+ }
+
+ void *id = rsElementCreate2(mRS->mContext,
+ (RsElement *)mElements.array(), fieldCount,
+ nameArray, fieldCount * sizeof(size_t), sizeArray,
+ (const uint32_t *)mArraySizes.array(), fieldCount);
+
+
+ free(nameArray);
+ free(sizeArray);
+
+ Element *e = new Element(id, mRS, mElements, mElementNames, mArraySizes);
+ return e;
+}
+
diff --git a/libs/rs/Element.h b/libs/rs/Element.h
new file mode 100644
index 000000000000..a579dc3003f3
--- /dev/null
+++ b/libs/rs/Element.h
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2008-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_ELEMENT_H__
+#define __ANDROID_ELEMENT_H__
+
+#include <rs.h>
+#include "RenderScript.h"
+#include "BaseObj.h"
+
+class Element : public BaseObj {
+public:
+ /**
+ * Return if a element is too complex for use as a data source for a Mesh or
+ * a Program.
+ *
+ * @return boolean
+ */
+ bool isComplex();
+
+ /**
+ * @hide
+ * @return number of sub-elements in this element
+ */
+ size_t getSubElementCount() {
+ return mVisibleElementMap.size();
+ }
+
+ /**
+ * @hide
+ * @param index index of the sub-element to return
+ * @return sub-element in this element at given index
+ */
+ const Element * getSubElement(uint32_t index);
+
+ /**
+ * @hide
+ * @param index index of the sub-element
+ * @return sub-element in this element at given index
+ */
+ const char * getSubElementName(uint32_t index);
+
+ /**
+ * @hide
+ * @param index index of the sub-element
+ * @return array size of sub-element in this element at given index
+ */
+ size_t getSubElementArraySize(uint32_t index);
+
+ /**
+ * @hide
+ * @param index index of the sub-element
+ * @return offset in bytes of sub-element in this element at given index
+ */
+ uint32_t getSubElementOffsetBytes(uint32_t index);
+
+ /**
+ * @hide
+ * @return element data type
+ */
+ RsDataType getDataType() const {
+ return mType;
+ }
+
+ /**
+ * @hide
+ * @return element data kind
+ */
+ RsDataKind getDataKind() const {
+ return mKind;
+ }
+
+ size_t getSizeBytes() const {
+ return mSizeBytes;
+ }
+
+
+ static const Element * BOOLEAN(RenderScript *rs);
+ static const Element * U8(RenderScript *rs);
+ static const Element * I8(RenderScript *rs);
+ static const Element * U16(RenderScript *rs);
+ static const Element * I16(RenderScript *rs);
+ static const Element * U32(RenderScript *rs);
+ static const Element * I32(RenderScript *rs);
+ static const Element * U64(RenderScript *rs);
+ static const Element * I64(RenderScript *rs);
+ static const Element * F32(RenderScript *rs);
+ static const Element * F64(RenderScript *rs);
+ static const Element * ELEMENT(RenderScript *rs);
+ static const Element * TYPE(RenderScript *rs);
+ static const Element * ALLOCATION(RenderScript *rs);
+ static const Element * SAMPLER(RenderScript *rs);
+ static const Element * SCRIPT(RenderScript *rs);
+ static const Element * MESH(RenderScript *rs);
+ static const Element * PROGRAM_FRAGMENT(RenderScript *rs);
+ static const Element * PROGRAM_VERTEX(RenderScript *rs);
+ static const Element * PROGRAM_RASTER(RenderScript *rs);
+ static const Element * PROGRAM_STORE(RenderScript *rs);
+
+ static const Element * A_8(RenderScript *rs);
+ static const Element * RGB_565(RenderScript *rs);
+ static const Element * RGB_888(RenderScript *rs);
+ static const Element * RGBA_5551(RenderScript *rs);
+ static const Element * RGBA_4444(RenderScript *rs);
+ static const Element * RGBA_8888(RenderScript *rs);
+
+ static const Element * F32_2(RenderScript *rs);
+ static const Element * F32_3(RenderScript *rs);
+ static const Element * F32_4(RenderScript *rs);
+ static const Element * F64_2(RenderScript *rs);
+ static const Element * F64_3(RenderScript *rs);
+ static const Element * F64_4(RenderScript *rs);
+ static const Element * U8_2(RenderScript *rs);
+ static const Element * U8_3(RenderScript *rs);
+ static const Element * U8_4(RenderScript *rs);
+ static const Element * I8_2(RenderScript *rs);
+ static const Element * I8_3(RenderScript *rs);
+ static const Element * I8_4(RenderScript *rs);
+ static const Element * U16_2(RenderScript *rs);
+ static const Element * U16_3(RenderScript *rs);
+ static const Element * U16_4(RenderScript *rs);
+ static const Element * I16_2(RenderScript *rs);
+ static const Element * I16_3(RenderScript *rs);
+ static const Element * I16_4(RenderScript *rs);
+ static const Element * U32_2(RenderScript *rs);
+ static const Element * U32_3(RenderScript *rs);
+ static const Element * U32_4(RenderScript *rs);
+ static const Element * I32_2(RenderScript *rs);
+ static const Element * I32_3(RenderScript *rs);
+ static const Element * I32_4(RenderScript *rs);
+ static const Element * U64_2(RenderScript *rs);
+ static const Element * U64_3(RenderScript *rs);
+ static const Element * U64_4(RenderScript *rs);
+ static const Element * I64_2(RenderScript *rs);
+ static const Element * I64_3(RenderScript *rs);
+ static const Element * I64_4(RenderScript *rs);
+ static const Element * MATRIX_4X4(RenderScript *rs);
+ static const Element * MATRIX_3X3(RenderScript *rs);
+ static const Element * MATRIX_2X2(RenderScript *rs);
+
+ Element(void *id, RenderScript *rs,
+ android::Vector<const Element *> &elements,
+ android::Vector<android::String8> &elementNames,
+ android::Vector<uint32_t> &arraySizes);
+ Element(void *id, RenderScript *rs, RsDataType dt, RsDataKind dk, bool norm, uint32_t size);
+ Element(RenderScript *rs);
+ virtual ~Element();
+
+ void updateFromNative();
+ static const Element * createUser(RenderScript *rs, RsDataType dt);
+ static const Element * createVector(RenderScript *rs, RsDataType dt, uint32_t size);
+ static const Element * createPixel(RenderScript *rs, RsDataType dt, RsDataKind dk);
+ bool isCompatible(const Element *e);
+
+ class Builder {
+ private:
+ RenderScript *mRS;
+ android::Vector<const Element *> mElements;
+ android::Vector<android::String8> mElementNames;
+ android::Vector<uint32_t> mArraySizes;
+ bool mSkipPadding;
+
+ public:
+ Builder(RenderScript *rs);
+ ~Builder();
+ void add(const Element *, android::String8 &name, uint32_t arraySize = 1);
+ const Element * create();
+ };
+
+private:
+ void updateVisibleSubElements();
+
+ android::Vector<const Element *> mElements;
+ android::Vector<android::String8> mElementNames;
+ android::Vector<uint32_t> mArraySizes;
+ android::Vector<uint32_t> mVisibleElementMap;
+ android::Vector<uint32_t> mOffsetInBytes;
+
+ RsDataType mType;
+ RsDataKind mKind;
+ bool mNormalized;
+ size_t mSizeBytes;
+ size_t mVectorSize;
+};
+
+#endif
diff --git a/libs/rs/RenderScript.cpp b/libs/rs/RenderScript.cpp
new file mode 100644
index 000000000000..39f102437adf
--- /dev/null
+++ b/libs/rs/RenderScript.cpp
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2008-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 <utils/Log.h>
+#include <malloc.h>
+#include <string.h>
+
+#include "RenderScript.h"
+
+bool RenderScript::gInitialized = false;
+pthread_mutex_t RenderScript::gInitMutex = PTHREAD_MUTEX_INITIALIZER;
+
+RenderScript::RenderScript() {
+ mDev = NULL;
+ mContext = NULL;
+ mErrorFunc = NULL;
+ mMessageFunc = NULL;
+ mMessageRun = false;
+
+ memset(&mElements, 0, sizeof(mElements));
+}
+
+RenderScript::~RenderScript() {
+ mMessageRun = false;
+
+ rsContextDeinitToClient(mContext);
+
+ void *res = NULL;
+ int status = pthread_join(mMessageThreadId, &res);
+
+ rsContextDestroy(mContext);
+ mContext = NULL;
+ rsDeviceDestroy(mDev);
+ mDev = NULL;
+}
+
+bool RenderScript::init(int targetApi) {
+ mDev = rsDeviceCreate();
+ if (mDev == 0) {
+ ALOGE("Device creation failed");
+ return false;
+ }
+
+ mContext = rsContextCreate(mDev, 0, targetApi);
+ if (mContext == 0) {
+ ALOGE("Context creation failed");
+ return false;
+ }
+
+
+ pid_t mNativeMessageThreadId;
+
+ int status = pthread_create(&mMessageThreadId, NULL, threadProc, this);
+ if (status) {
+ ALOGE("Failed to start RenderScript message thread.");
+ return false;
+ }
+ // Wait for the message thread to be active.
+ while (!mMessageRun) {
+ usleep(1000);
+ }
+
+ return true;
+}
+
+void RenderScript::throwError(const char *err) const {
+ ALOGE("RS CPP error: %s", err);
+ int * v = NULL;
+ v[0] = 0;
+}
+
+
+void * RenderScript::threadProc(void *vrsc) {
+ RenderScript *rs = static_cast<RenderScript *>(vrsc);
+ size_t rbuf_size = 256;
+ void * rbuf = malloc(rbuf_size);
+
+ rsContextInitToClient(rs->mContext);
+ rs->mMessageRun = true;
+
+ while (rs->mMessageRun) {
+ size_t receiveLen = 0;
+ uint32_t usrID = 0;
+ uint32_t subID = 0;
+ RsMessageToClientType r = rsContextPeekMessage(rs->mContext,
+ &receiveLen, sizeof(receiveLen),
+ &usrID, sizeof(usrID));
+
+ if (receiveLen >= rbuf_size) {
+ rbuf_size = receiveLen + 32;
+ rbuf = realloc(rbuf, rbuf_size);
+ }
+ if (!rbuf) {
+ ALOGE("RenderScript::message handler realloc error %zu", rbuf_size);
+ // No clean way to recover now?
+ }
+ rsContextGetMessage(rs->mContext, rbuf, rbuf_size, &receiveLen, sizeof(receiveLen),
+ &subID, sizeof(subID));
+
+ switch(r) {
+ case RS_MESSAGE_TO_CLIENT_ERROR:
+ ALOGE("RS Error %s", (const char *)rbuf);
+
+ if(rs->mMessageFunc != NULL) {
+ rs->mErrorFunc(usrID, (const char *)rbuf);
+ }
+ break;
+ case RS_MESSAGE_TO_CLIENT_EXCEPTION:
+ // teardown. But we want to avoid starving other threads during
+ // teardown by yielding until the next line in the destructor can
+ // execute to set mRun = false
+ usleep(1000);
+ break;
+ case RS_MESSAGE_TO_CLIENT_USER:
+ if(rs->mMessageFunc != NULL) {
+ rs->mMessageFunc(usrID, rbuf, receiveLen);
+ } else {
+ ALOGE("Received a message from the script with no message handler installed.");
+ }
+ break;
+
+ default:
+ ALOGE("RenderScript unknown message type %i", r);
+ }
+ }
+
+ if (rbuf) {
+ free(rbuf);
+ }
+ ALOGE("RenderScript Message thread exiting.");
+ return NULL;
+}
+
+void RenderScript::setErrorHandler(ErrorHandlerFunc_t func) {
+ mErrorFunc = func;
+}
+
+void RenderScript::setMessageHandler(MessageHandlerFunc_t func) {
+ mMessageFunc = func;
+}
+
+void RenderScript::contextDump() {
+}
+
+void RenderScript::finish() {
+
+}
+
+
diff --git a/libs/rs/RenderScript.h b/libs/rs/RenderScript.h
new file mode 100644
index 000000000000..0eb6a6d6c82f
--- /dev/null
+++ b/libs/rs/RenderScript.h
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2008-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_RENDERSCRIPT_H
+#define ANDROID_RENDERSCRIPT_H
+
+
+#include <pthread.h>
+#include <utils/String8.h>
+#include <utils/Vector.h>
+
+#include "rs.h"
+
+class Element;
+class Type;
+class Allocation;
+
+class RenderScript {
+ friend class BaseObj;
+ friend class Allocation;
+ friend class Element;
+ friend class Type;
+ friend class Script;
+ friend class ScriptC;
+
+public:
+ RenderScript();
+ virtual ~RenderScript();
+
+ typedef void (*ErrorHandlerFunc_t)(uint32_t errorNum, const char *errorText);
+ typedef void (*MessageHandlerFunc_t)(uint32_t msgNum, const void *msgData, size_t msgLen);
+
+
+ void setErrorHandler(ErrorHandlerFunc_t func);
+ ErrorHandlerFunc_t getErrorHandler() {return mErrorFunc;}
+
+ void setMessageHandler(MessageHandlerFunc_t func);
+ MessageHandlerFunc_t getMessageHandler() {return mMessageFunc;}
+
+ bool init(int targetApi);
+ void contextDump();
+ void finish();
+
+private:
+ static bool gInitialized;
+ static pthread_mutex_t gInitMutex;
+
+ pthread_t mMessageThreadId;
+ pid_t mNativeMessageThreadId;
+ bool mMessageRun;
+
+ RsDevice mDev;
+ RsContext mContext;
+
+ ErrorHandlerFunc_t mErrorFunc;
+ MessageHandlerFunc_t mMessageFunc;
+
+ struct {
+ Element *U8;
+ Element *I8;
+ Element *U16;
+ Element *I16;
+ Element *U32;
+ Element *I32;
+ Element *U64;
+ Element *I64;
+ Element *F32;
+ Element *F64;
+ Element *BOOLEAN;
+
+ Element *ELEMENT;
+ Element *TYPE;
+ Element *ALLOCATION;
+ Element *SAMPLER;
+ Element *SCRIPT;
+ Element *MESH;
+ Element *PROGRAM_FRAGMENT;
+ Element *PROGRAM_VERTEX;
+ Element *PROGRAM_RASTER;
+ Element *PROGRAM_STORE;
+
+ Element *A_8;
+ Element *RGB_565;
+ Element *RGB_888;
+ Element *RGBA_5551;
+ Element *RGBA_4444;
+ Element *RGBA_8888;
+
+ Element *FLOAT_2;
+ Element *FLOAT_3;
+ Element *FLOAT_4;
+
+ Element *DOUBLE_2;
+ Element *DOUBLE_3;
+ Element *DOUBLE_4;
+
+ Element *UCHAR_2;
+ Element *UCHAR_3;
+ Element *UCHAR_4;
+
+ Element *CHAR_2;
+ Element *CHAR_3;
+ Element *CHAR_4;
+
+ Element *USHORT_2;
+ Element *USHORT_3;
+ Element *USHORT_4;
+
+ Element *SHORT_2;
+ Element *SHORT_3;
+ Element *SHORT_4;
+
+ Element *UINT_2;
+ Element *UINT_3;
+ Element *UINT_4;
+
+ Element *INT_2;
+ Element *INT_3;
+ Element *INT_4;
+
+ Element *ULONG_2;
+ Element *ULONG_3;
+ Element *ULONG_4;
+
+ Element *LONG_2;
+ Element *LONG_3;
+ Element *LONG_4;
+
+ Element *MATRIX_4X4;
+ Element *MATRIX_3X3;
+ Element *MATRIX_2X2;
+ } mElements;
+
+
+
+ void throwError(const char *err) const;
+
+ static void * threadProc(void *);
+
+};
+
+#endif
+
diff --git a/libs/rs/Script.cpp b/libs/rs/Script.cpp
new file mode 100644
index 000000000000..b6112dd7c927
--- /dev/null
+++ b/libs/rs/Script.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2008-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 <utils/Log.h>
+#include <malloc.h>
+
+#include "RenderScript.h"
+#include "Element.h"
+#include "Type.h"
+#include "Allocation.h"
+#include "Script.h"
+
+void Script::invoke(uint32_t slot, const void *v, size_t len) {
+ rsScriptInvokeV(mRS->mContext, getID(), slot, v, len);
+}
+
+void Script::forEach(uint32_t slot, const Allocation *ain, const Allocation *aout,
+ const void *usr, size_t usrLen) {
+ if ((ain == NULL) && (aout == NULL)) {
+ mRS->throwError("At least one of ain or aout is required to be non-null.");
+ }
+ void *in_id = BaseObj::getObjID(ain);
+ void *out_id = BaseObj::getObjID(aout);
+ rsScriptForEach(mRS->mContext, getID(), slot, in_id, out_id, usr, usrLen);
+}
+
+
+Script::Script(void *id, RenderScript *rs) : BaseObj(id, rs) {
+}
+
+
+void Script::bindAllocation(const Allocation *va, uint32_t slot) {
+ rsScriptBindAllocation(mRS->mContext, getID(), BaseObj::getObjID(va), slot);
+}
+
+
+void Script::setVar(uint32_t index, const BaseObj *o) {
+ rsScriptSetVarObj(mRS->mContext, getID(), index, (o == NULL) ? 0 : o->getID());
+}
+
+void Script::setVar(uint32_t index, const void *v, size_t len) {
+ rsScriptSetVarV(mRS->mContext, getID(), index, v, len);
+}
+
+
+
+void Script::FieldBase::init(RenderScript *rs, uint32_t dimx, uint32_t usages) {
+ mAllocation = Allocation::createSized(rs, mElement, dimx, RS_ALLOCATION_USAGE_SCRIPT | usages);
+}
+
+//Script::FieldBase::FieldBase() {
+//}
+
+
diff --git a/libs/rs/Script.h b/libs/rs/Script.h
new file mode 100644
index 000000000000..54d1e40cfd2f
--- /dev/null
+++ b/libs/rs/Script.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2008-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_SCRIPT_H__
+#define __ANDROID_SCRIPT_H__
+
+#include <pthread.h>
+#include <rs.h>
+
+#include "RenderScript.h"
+#include "Allocation.h"
+
+class Type;
+class Element;
+class Allocation;
+
+class Script : public BaseObj {
+protected:
+ Script(void *id, RenderScript *rs);
+ void forEach(uint32_t slot, const Allocation *in, const Allocation *out, const void *v, size_t);
+ void bindAllocation(const Allocation *va, uint32_t slot);
+ void setVar(uint32_t index, const void *, size_t len);
+ void setVar(uint32_t index, const BaseObj *o);
+ void invoke(uint32_t slot, const void *v, size_t len);
+
+
+ void invoke(uint32_t slot) {
+ invoke(slot, NULL, 0);
+ }
+ void setVar(uint32_t index, float v) {
+ setVar(index, &v, sizeof(v));
+ }
+ void setVar(uint32_t index, double v) {
+ setVar(index, &v, sizeof(v));
+ }
+ void setVar(uint32_t index, int32_t v) {
+ setVar(index, &v, sizeof(v));
+ }
+ void setVar(uint32_t index, int64_t v) {
+ setVar(index, &v, sizeof(v));
+ }
+ void setVar(uint32_t index, bool v) {
+ setVar(index, &v, sizeof(v));
+ }
+
+public:
+ class FieldBase {
+ protected:
+ const Element *mElement;
+ Allocation *mAllocation;
+
+ void init(RenderScript *rs, uint32_t dimx, uint32_t usages = 0);
+
+ public:
+ const Element *getElement() {
+ return mElement;
+ }
+
+ const Type *getType() {
+ return mAllocation->getType();
+ }
+
+ const Allocation *getAllocation() {
+ return mAllocation;
+ }
+
+ //void updateAllocation();
+ };
+};
+
+#endif
diff --git a/libs/rs/ScriptC.cpp b/libs/rs/ScriptC.cpp
new file mode 100644
index 000000000000..53d75b8501cc
--- /dev/null
+++ b/libs/rs/ScriptC.cpp
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2008-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 <utils/Log.h>
+#include <malloc.h>
+
+#include "ScriptC.h"
+
+ScriptC::ScriptC(RenderScript *rs,
+ const char *codeTxt, size_t codeLength,
+ const char *cachedName, size_t cachedNameLength,
+ const char *cacheDir, size_t cacheDirLength)
+: Script(NULL, rs) {
+ mID = rsScriptCCreate(rs->mContext, cachedName, cachedNameLength,
+ cacheDir, cacheDirLength, codeTxt, codeLength);
+}
+
diff --git a/libs/rs/ScriptC.h b/libs/rs/ScriptC.h
new file mode 100644
index 000000000000..25f00ec52a7f
--- /dev/null
+++ b/libs/rs/ScriptC.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2008-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_SCRIPTC_H__
+#define __ANDROID_SCRIPTC_H__
+
+#include <pthread.h>
+#include <rs.h>
+
+#include "Script.h"
+
+class ScriptC : public Script {
+protected:
+ ScriptC(RenderScript *rs, void *txt, size_t len);
+ ScriptC(RenderScript *rs,
+ const char *codeTxt, size_t codeLength,
+ const char *cachedName, size_t cachedNameLength,
+ const char *cacheDir, size_t cacheDirLength);
+
+};
+
+#endif
diff --git a/libs/rs/Type.cpp b/libs/rs/Type.cpp
new file mode 100644
index 000000000000..3249f971d451
--- /dev/null
+++ b/libs/rs/Type.cpp
@@ -0,0 +1,159 @@
+/*
+ * 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.
+ */
+
+#include <utils/Log.h>
+#include <malloc.h>
+#include <string.h>
+
+#include "RenderScript.h"
+#include "Element.h"
+#include "Type.h"
+
+void Type::calcElementCount() {
+ bool hasLod = hasMipmaps();
+ uint32_t x = getX();
+ uint32_t y = getY();
+ uint32_t z = getZ();
+ uint32_t faces = 1;
+ if (hasFaces()) {
+ faces = 6;
+ }
+ if (x == 0) {
+ x = 1;
+ }
+ if (y == 0) {
+ y = 1;
+ }
+ if (z == 0) {
+ z = 1;
+ }
+
+ uint32_t count = x * y * z * faces;
+ while (hasLod && ((x > 1) || (y > 1) || (z > 1))) {
+ if(x > 1) {
+ x >>= 1;
+ }
+ if(y > 1) {
+ y >>= 1;
+ }
+ if(z > 1) {
+ z >>= 1;
+ }
+
+ count += x * y * z * faces;
+ }
+ mElementCount = count;
+}
+
+
+Type::Type(void *id, RenderScript *rs) : BaseObj(id, rs) {
+ mDimX = 0;
+ mDimY = 0;
+ mDimZ = 0;
+ mDimMipmaps = false;
+ mDimFaces = false;
+ mElement = NULL;
+}
+
+void Type::updateFromNative() {
+ // We have 6 integer to obtain mDimX; mDimY; mDimZ;
+ // mDimLOD; mDimFaces; mElement;
+
+ /*
+ int[] dataBuffer = new int[6];
+ mRS.nTypeGetNativeData(getID(), dataBuffer);
+
+ mDimX = dataBuffer[0];
+ mDimY = dataBuffer[1];
+ mDimZ = dataBuffer[2];
+ mDimMipmaps = dataBuffer[3] == 1 ? true : false;
+ mDimFaces = dataBuffer[4] == 1 ? true : false;
+
+ int elementID = dataBuffer[5];
+ if(elementID != 0) {
+ mElement = new Element(elementID, mRS);
+ mElement.updateFromNative();
+ }
+ calcElementCount();
+ */
+}
+
+Type::Builder::Builder(RenderScript *rs, const Element *e) {
+ mRS = rs;
+ mElement = e;
+ mDimX = 0;
+ mDimY = 0;
+ mDimZ = 0;
+ mDimMipmaps = false;
+ mDimFaces = false;
+}
+
+void Type::Builder::setX(uint32_t value) {
+ if(value < 1) {
+ ALOGE("Values of less than 1 for Dimension X are not valid.");
+ }
+ mDimX = value;
+}
+
+void Type::Builder::setY(int value) {
+ if(value < 1) {
+ ALOGE("Values of less than 1 for Dimension Y are not valid.");
+ }
+ mDimY = value;
+}
+
+void Type::Builder::setMipmaps(bool value) {
+ mDimMipmaps = value;
+}
+
+void Type::Builder::setFaces(bool value) {
+ mDimFaces = value;
+}
+
+const Type * Type::Builder::create() {
+ ALOGE(" %i %i %i %i %i", mDimX, mDimY, mDimZ, mDimFaces, mDimMipmaps);
+ if (mDimZ > 0) {
+ if ((mDimX < 1) || (mDimY < 1)) {
+ ALOGE("Both X and Y dimension required when Z is present.");
+ }
+ if (mDimFaces) {
+ ALOGE("Cube maps not supported with 3D types.");
+ }
+ }
+ if (mDimY > 0) {
+ if (mDimX < 1) {
+ ALOGE("X dimension required when Y is present.");
+ }
+ }
+ if (mDimFaces) {
+ if (mDimY < 1) {
+ ALOGE("Cube maps require 2D Types.");
+ }
+ }
+
+ void * id = rsTypeCreate(mRS->mContext, mElement->getID(), mDimX, mDimY, mDimZ, mDimMipmaps, mDimFaces);
+ Type *t = new Type(id, mRS);
+ t->mElement = mElement;
+ t->mDimX = mDimX;
+ t->mDimY = mDimY;
+ t->mDimZ = mDimZ;
+ t->mDimMipmaps = mDimMipmaps;
+ t->mDimFaces = mDimFaces;
+
+ t->calcElementCount();
+ return t;
+}
+
diff --git a/libs/rs/Type.h b/libs/rs/Type.h
new file mode 100644
index 000000000000..53481c3078de
--- /dev/null
+++ b/libs/rs/Type.h
@@ -0,0 +1,99 @@
+/*
+ * 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.
+ */
+
+#ifndef __ANDROID_TYPE_H__
+#define __ANDROID_TYPE_H__
+
+#include <rs.h>
+#include "RenderScript.h"
+#include "Element.h"
+
+class Type : public BaseObj {
+protected:
+ friend class Allocation;
+
+ uint32_t mDimX;
+ uint32_t mDimY;
+ uint32_t mDimZ;
+ bool mDimMipmaps;
+ bool mDimFaces;
+ size_t mElementCount;
+ const Element *mElement;
+
+ void calcElementCount();
+ virtual void updateFromNative();
+
+public:
+
+ const Element* getElement() const {
+ return mElement;
+ }
+
+ uint32_t getX() const {
+ return mDimX;
+ }
+
+ uint32_t getY() const {
+ return mDimY;
+ }
+
+ uint32_t getZ() const {
+ return mDimZ;
+ }
+
+ bool hasMipmaps() const {
+ return mDimMipmaps;
+ }
+
+ bool hasFaces() const {
+ return mDimFaces;
+ }
+
+ size_t getCount() const {
+ return mElementCount;
+ }
+
+ size_t getSizeBytes() const {
+ return mElementCount * mElement->getSizeBytes();
+ }
+
+
+ Type(void *id, RenderScript *rs);
+
+
+ class Builder {
+ protected:
+ RenderScript *mRS;
+ uint32_t mDimX;
+ uint32_t mDimY;
+ uint32_t mDimZ;
+ bool mDimMipmaps;
+ bool mDimFaces;
+ const Element *mElement;
+
+ public:
+ Builder(RenderScript *rs, const Element *e);
+
+ void setX(uint32_t value);
+ void setY(int value);
+ void setMipmaps(bool value);
+ void setFaces(bool value);
+ const Type * create();
+ };
+
+};
+
+#endif
diff --git a/libs/rs/driver/rsdBcc.cpp b/libs/rs/driver/rsdBcc.cpp
index bec6ffffce01..dd7868408235 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 @@ extern "C" {
#include "libdex/ZipArchive.h"
}
-
using namespace android;
using namespace android::renderscript;
@@ -45,6 +43,7 @@ struct DrvScript {
bcinfo::MetadataExtractor *ME;
InvokeFunc_t *mInvokeFunctions;
+ ForEachFunc_t *mForEachFunctions;
void ** mFieldAddress;
bool * mFieldIsObject;
const uint32_t *mExportForEachSignatureList;
@@ -162,8 +161,16 @@ bool rsdScriptInit(const Context *rsc,
}
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 @@ error:
typedef struct {
Context *rsc;
Script *script;
+ ForEachFunc_t kernel;
uint32_t sig;
const Allocation * ain;
Allocation * aout;
@@ -235,7 +243,7 @@ static void wc_xy(void *usr, uint32_t idx) {
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 @@ static void wc_x(void *usr, uint32_t idx) {
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 @@ void rsdScriptInvokeForEach(const Context *rsc,
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 @@ void rsdScriptInvokeForEach(const Context *rsc,
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 @@ void rsdScriptDestroy(const Context *dc, Script *script) {
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 168bdf36e1cb..05ca13bb35a2 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 357dbe32e18e..6a3bd4bf7a08 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 @@ void rsi_ScriptForEach(Context *rsc, RsScript vs, uint32_t slot,
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 99dceafaba37..7879ea61c50e 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 @@ public:
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 b4eb995aed2f..79725b97e03f 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 @@ uint32_t ScriptC::run(Context *rsc) {
void ScriptC::runForEach(Context *rsc,
+ uint32_t slot,
const Allocation * ain,
Allocation * aout,
const void * usr,
@@ -138,7 +139,7 @@ void ScriptC::runForEach(Context *rsc,
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 fc4df5132c23..92e1f4ff3ec0 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 @@ public:
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 183e207c7470..a5a0fae9bf57 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 @@ void rsrForEach(Context *rsc, Script *sc,
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/rs/tests/Android.mk b/libs/rs/tests/Android.mk
new file mode 100644
index 000000000000..a773e84b449c
--- /dev/null
+++ b/libs/rs/tests/Android.mk
@@ -0,0 +1,29 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+ compute.cpp
+
+LOCAL_SHARED_LIBRARIES := \
+ libRS \
+ libz \
+ libcutils \
+ libutils \
+ libEGL \
+ libGLESv1_CM \
+ libGLESv2 \
+ libui \
+ libbcc \
+ libbcinfo \
+ libgui
+
+LOCAL_MODULE:= rstest-compute
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_C_INCLUDES += .. \
+ frameworks/base/libs/rs \
+ out/target/product/stingray/obj/SHARED_LIBRARIES/libRS_intermediates
+
+include $(BUILD_EXECUTABLE)
+
diff --git a/libs/rs/tests/compute.cpp b/libs/rs/tests/compute.cpp
new file mode 100644
index 000000000000..28b135fba261
--- /dev/null
+++ b/libs/rs/tests/compute.cpp
@@ -0,0 +1,37 @@
+
+#include "RenderScript.h"
+#include "Element.h"
+#include "Type.h"
+#include "Allocation.h"
+
+int main(int argc, char** argv)
+{
+
+ RenderScript *rs = new RenderScript();
+ printf("New RS %p\n", rs);
+
+ bool r = rs->init(16);
+ printf("Init returned %i\n", r);
+
+ const Element *e = Element::RGBA_8888(rs);
+ printf("Element %p\n", e);
+
+ Type::Builder tb(rs, e);
+ tb.setX(128);
+ tb.setY(128);
+ const Type *t = tb.create();
+ printf("Type %p\n", t);
+
+
+ const Allocation *a1 = Allocation::createSized(rs, e, 1000);
+ printf("Allocation %p\n", a1);
+
+
+
+ printf("Deleting stuff\n");
+ delete t;
+ delete a1;
+ delete e;
+ delete rs;
+ printf("Delete OK\n");
+}
diff --git a/libs/ui/Android.mk b/libs/ui/Android.mk
index f0d7b02e67b5..c0292915db42 100644
--- a/libs/ui/Android.mk
+++ b/libs/ui/Android.mk
@@ -31,6 +31,10 @@ LOCAL_SHARED_LIBRARIES := \
libEGL \
libhardware
+ifneq ($(BOARD_FRAMEBUFFER_FORCE_FORMAT),)
+LOCAL_CFLAGS += -DFRAMEBUFFER_FORCE_FORMAT=$(BOARD_FRAMEBUFFER_FORCE_FORMAT)
+endif
+
LOCAL_MODULE:= libui
include $(BUILD_SHARED_LIBRARY)
diff --git a/libs/ui/FramebufferNativeWindow.cpp b/libs/ui/FramebufferNativeWindow.cpp
index d1dca0c906db..26d4823db110 100644
--- a/libs/ui/FramebufferNativeWindow.cpp
+++ b/libs/ui/FramebufferNativeWindow.cpp
@@ -100,6 +100,18 @@ FramebufferNativeWindow::FramebufferNativeWindow()
mNumFreeBuffers = NUM_FRAME_BUFFERS;
mBufferHead = mNumBuffers-1;
+ /*
+ * This does not actually change the framebuffer format. It merely
+ * fakes this format to surfaceflinger so that when it creates
+ * framebuffer surfaces it will use this format. It's really a giant
+ * HACK to allow interworking with buggy gralloc+GPU driver
+ * implementations. You should *NEVER* need to set this for shipping
+ * devices.
+ */
+#ifdef FRAMEBUFFER_FORCE_FORMAT
+ *((uint32_t *)&fbDev->format) = FRAMEBUFFER_FORCE_FORMAT;
+#endif
+
for (i = 0; i < mNumBuffers; i++)
{
buffers[i] = new NativeBuffer(
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
new file mode 100644
index 000000000000..7f496ca41cf0
--- /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 000000000000..6a7f2f57f3dc
--- /dev/null
+++ b/media/java/android/media/MediaExtractor.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import java.nio.ByteBuffer;
+import java.util.Map;
+
+/**
+ * MediaExtractor
+ * @hide
+*/
+public class MediaExtractor
+{
+ public MediaExtractor(String path) {
+ native_setup(path);
+ }
+
+ @Override
+ protected void finalize() {
+ native_finalize();
+ }
+
+ // Make sure you call this when you're done to free up any resources
+ // instead of relying on the garbage collector to do this for you at
+ // some point in the future.
+ public native final void release();
+
+ public native int countTracks();
+ public native Map<String, Object> getTrackFormat(int index);
+
+ // Subsequent calls to "readSampleData", "getSampleTrackIndex" and
+ // "getSampleTime" only retrieve information for the subset of tracks
+ // selected by the call below.
+ // Selecting the same track multiple times has no effect, the track
+ // is only selected once.
+ public native void selectTrack(int index);
+
+ // All selected tracks seek near the requested time. The next sample
+ // returned for each selected track will be a sync sample.
+ public native void seekTo(long timeUs);
+
+ public native boolean advance();
+
+ // Retrieve the current encoded sample and store it in the byte buffer
+ // starting at the given offset.
+ public native int readSampleData(ByteBuffer byteBuf, int offset);
+
+ // Returns the track index the current sample originates from.
+ public native int getSampleTrackIndex();
+
+ // Returns the current sample's presentation time in microseconds.
+ public native long getSampleTime();
+
+ private static native final void native_init();
+ private native final void native_setup(String path);
+ private native final void native_finalize();
+
+ static {
+ System.loadLibrary("media_jni");
+ native_init();
+ }
+
+ private int mNativeContext;
+}
diff --git a/media/jni/Android.mk b/media/jni/Android.mk
index 23cc0e2f3228..070d2d93a8c2 100644
--- a/media/jni/Android.mk
+++ b/media/jni/Android.mk
@@ -2,6 +2,8 @@ LOCAL_PATH:= $(call my-dir)
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 @@ LOCAL_SHARED_LIBRARIES := \
libcutils \
libgui \
libstagefright \
+ libstagefright_foundation \
libcamera_client \
libmtp \
libusbhost \
@@ -39,10 +42,12 @@ LOCAL_C_INCLUDES += \
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 000000000000..43ca263a0301
--- /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 000000000000..6b1257db74b1
--- /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 000000000000..4757adfc6429
--- /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 000000000000..70e58c6c1a26
--- /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 8ff9dd3fdc02..199d56e469f9 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -810,6 +810,8 @@ static int register_android_media_MediaPlayer(JNIEnv *env)
"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 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved)
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 47963b101b7d..8b2321c31288 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 @@ bool ConvertKeyValueArraysToKeyedVector(
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 a2c155adfc7a..635bceb2fddf 100644
--- a/media/jni/android_media_Utils.h
+++ b/media/jni/android_media_Utils.h
@@ -33,6 +33,14 @@ bool ConvertKeyValueArraysToKeyedVector(
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/libmediaplayerservice/nuplayer/NuPlayer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
index b731d0f3cd58..dec1c08c16fb 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
@@ -387,10 +387,10 @@ void NuPlayer::onMessageReceived(const sp<AMessage> &msg) {
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 @@ status_t NuPlayer::feedDecoderInputData(bool audio, const sp<AMessage> &msg) {
mediaTimeUs / 1E6);
#endif
- reply->setObject("buffer", accessUnit);
+ reply->setBuffer("buffer", accessUnit);
reply->post();
return OK;
@@ -793,10 +793,8 @@ void NuPlayer::renderBuffer(bool audio, const sp<AMessage> &msg) {
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 56c2773fa56b..2a518292b19b 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp
@@ -214,8 +214,6 @@ sp<AMessage> NuPlayer::Decoder::makeFormat(const sp<MetaData> &meta) {
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 @@ void NuPlayer::Decoder::onFillThisBuffer(const sp<AMessage> &msg) {
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 @@ void NuPlayer::Decoder::onFillThisBuffer(const sp<AMessage> &msg) {
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 15259cb5c370..5738ecb60b52 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp
@@ -60,7 +60,7 @@ void NuPlayer::Renderer::queueBuffer(
const sp<AMessage> &notifyConsumed) {
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 @@ void NuPlayer::Renderer::onQueueBuffer(const sp<AMessage> &msg) {
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", &notifyConsumed));
diff --git a/media/libmediaplayerservice/nuplayer/RTSPSource.cpp b/media/libmediaplayerservice/nuplayer/RTSPSource.cpp
index 6eb0d077aac2..4c65b6564df5 100644
--- a/media/libmediaplayerservice/nuplayer/RTSPSource.cpp
+++ b/media/libmediaplayerservice/nuplayer/RTSPSource.cpp
@@ -218,10 +218,8 @@ void NuPlayer::RTSPSource::onMessageReceived(const sp<AMessage> &msg) {
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 ca44ea364024..c91fbe6a2351 100644
--- a/media/libstagefright/ACodec.cpp
+++ b/media/libstagefright/ACodec.cpp
@@ -171,6 +171,9 @@ protected:
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 @@ protected:
private:
void changeStateIfWeOwnAllBuffers();
+ bool mComponentNowIdle;
+
DISALLOW_EVIL_CONSTRUCTORS(ExecutingToIdleState);
};
@@ -309,7 +314,8 @@ private:
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 @@ void ACodec::initiateSetup(const sp<AMessage> &msg) {
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 @@ status_t ACodec::allocateBuffersOnPort(OMX_U32 portIndex) {
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;
- OMX_PARAM_PORTDEFINITIONTYPE def;
- InitOMXParams(&def);
- def.nPortIndex = portIndex;
+ err = mOMX->getParameter(
+ mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
- status_t 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");
- if (err != OK) {
- return err;
- }
+ size_t totalSize = def.nBufferCountActual * def.nBufferSize;
+ mDealer[portIndex] = new MemoryDealer(totalSize, "ACodec");
- ALOGV("[%s] Allocating %lu buffers of size %lu on %s port",
- mComponentName.c_str(),
- def.nBufferCountActual, def.nBufferSize,
- portIndex == kPortIndexInput ? "input" : "output");
+ for (OMX_U32 i = 0; i < def.nBufferCountActual; ++i) {
+ sp<IMemory> mem = mDealer[portIndex]->allocate(def.nBufferSize);
+ CHECK(mem.get() != NULL);
- size_t totalSize = def.nBufferCountActual * def.nBufferSize;
- mDealer[portIndex] = new MemoryDealer(totalSize, "OMXCodec");
+ IOMX::buffer_id buffer;
- for (OMX_U32 i = 0; i < def.nBufferCountActual; ++i) {
- sp<IMemory> mem = mDealer[portIndex]->allocate(def.nBufferSize);
- CHECK(mem.get() != NULL);
+ if (!strncasecmp(
+ mComponentName.c_str(), "OMX.TI.DUCATI1.VIDEO.", 21)) {
+ if (portIndex == kPortIndexInput && i == 0) {
+ // Only log this warning once per allocation round.
- IOMX::buffer_id buffer;
+ ALOGW("OMX.TI.DUCATI1.VIDEO.* require the use of "
+ "OMX_AllocateBuffer instead of the preferred "
+ "OMX_UseBuffer. Vendor must fix this.");
+ }
- if (!strcasecmp(
- mComponentName.c_str(), "OMX.TI.DUCATI1.VIDEO.DECODER")) {
- if (portIndex == kPortIndexInput && i == 0) {
- // Only log this warning once per allocation round.
+ err = mOMX->allocateBufferWithBackup(
+ mNode, portIndex, mem, &buffer);
+ } else {
+ err = mOMX->useBuffer(mNode, portIndex, mem, &buffer);
+ }
- ALOGW("OMX.TI.DUCATI1.VIDEO.DECODER requires the use of "
- "OMX_AllocateBuffer instead of the preferred "
- "OMX_UseBuffer. Vendor must fix this.");
+ BufferInfo info;
+ info.mBufferID = buffer;
+ info.mStatus = BufferInfo::OWNED_BY_US;
+ info.mData = new ABuffer(mem->pointer(), def.nBufferSize);
+ mBuffers[portIndex].push(info);
}
-
- err = mOMX->allocateBufferWithBackup(
- mNode, portIndex, mem, &buffer);
- } else {
- err = mOMX->useBuffer(mNode, portIndex, mem, &buffer);
}
+ }
- if (err != OK) {
- return err;
- }
+ 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);
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", ACodec::kWhatBuffersAllocated);
+
+ 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);
+
+ name = StringPrintf("data_%d", i);
+ notify->setBuffer(name.c_str(), mBuffers[portIndex][i].mData);
}
+ notify->post();
+
return OK;
}
@@ -671,7 +706,7 @@ ACodec::BufferInfo *ACodec::findBufferByID(
return NULL;
}
-void ACodec::setComponentRole(
+status_t ACodec::setComponentRole(
bool isEncoder, const char *mime) {
struct MimeToRole {
const char *mime;
@@ -700,6 +735,8 @@ void ACodec::setComponentRole(
"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 @@ void ACodec::setComponentRole(
}
if (i == kNumMimeToRole) {
- return;
+ return ERROR_UNSUPPORTED;
}
const char *role =
@@ -736,50 +773,83 @@ void ACodec::setComponentRole(
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;
+ }
- if (!strncasecmp(mime, "video/", 6)) {
- int32_t width, height;
- CHECK(msg->findInt32("width", &width));
- CHECK(msg->findInt32("height", &height));
+ mIsEncoder = encoder;
- CHECK_EQ(setupVideoDecoder(mime, width, height),
- (status_t)OK);
+ 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)) {
+ 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 @@ status_t ACodec::setMinBufferSize(OMX_U32 portIndex, size_t size) {
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 @@ status_t ACodec::setupAACDecoder(int32_t numChannels, int32_t sampleRate) {
profile.nSampleRate = sampleRate;
profile.eAACStreamFormat = OMX_AUDIO_AACStreamFormatMP4ADTS;
- err = mOMX->setParameter(
+ return mOMX->setParameter(
mNode, OMX_IndexParamAudioAac, &profile, sizeof(profile));
+}
- return err;
+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::setupAMRDecoder(bool isWAMR) {
+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 @@ status_t ACodec::setupAMRDecoder(bool isWAMR) {
}
def.eAMRFrameFormat = OMX_AUDIO_AMRFrameFormatFSF;
+ def.eAMRBandMode = pickModeFromBitRate(isWAMR, bitrate);
+
+ err = mOMX->setParameter(
+ mNode, OMX_IndexParamAudioAmr, &def, sizeof(def));
- def.eAMRBandMode =
- isWAMR ? OMX_AUDIO_AMRBandModeWB0 : OMX_AUDIO_AMRBandModeNB0;
+ if (err != OK) {
+ return err;
+ }
- return mOMX->setParameter(mNode, OMX_IndexParamAudioAmr, &def, sizeof(def));
+ 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 @@ status_t ACodec::setSupportedOutputFormat() {
&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 @@ status_t ACodec::setupVideoDecoder(
return OK;
}
+status_t ACodec::setupVideoEncoder(const char *mime, const sp<AMessage> &msg) {
+ int32_t tmp;
+ if (!msg->findInt32("color-format", &tmp)) {
+ return INVALID_OPERATION;
+ }
+
+ OMX_COLOR_FORMATTYPE colorFormat =
+ static_cast<OMX_COLOR_FORMATTYPE>(tmp);
+
+ status_t err = setVideoPortFormatType(
+ kPortIndexInput, OMX_VIDEO_CodingUnused, colorFormat);
+
+ if (err != OK) {
+ ALOGE("[%s] does not support color format %d",
+ mComponentName.c_str(), colorFormat);
+
+ return err;
+ }
+
+ /* Input port configuration */
+
+ OMX_PARAM_PORTDEFINITIONTYPE def;
+ InitOMXParams(&def);
+
+ OMX_VIDEO_PORTDEFINITIONTYPE *video_def = &def.format.video;
+
+ def.nPortIndex = kPortIndexInput;
+
+ err = mOMX->getParameter(
+ mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
+
+ if (err != OK) {
+ return err;
+ }
+
+ int32_t width, height, bitrate;
+ if (!msg->findInt32("width", &width)
+ || !msg->findInt32("height", &height)
+ || !msg->findInt32("bitrate", &bitrate)) {
+ return INVALID_OPERATION;
+ }
+
+ video_def->nFrameWidth = width;
+ video_def->nFrameHeight = height;
+
+ int32_t stride;
+ if (!msg->findInt32("stride", &stride)) {
+ stride = width;
+ }
+
+ video_def->nStride = stride;
+
+ int32_t sliceHeight;
+ if (!msg->findInt32("slice-height", &sliceHeight)) {
+ sliceHeight = height;
+ }
+
+ video_def->nSliceHeight = sliceHeight;
+
+ def.nBufferSize = (video_def->nStride * video_def->nSliceHeight * 3) / 2;
+
+ float frameRate;
+ if (!msg->findFloat("frame-rate", &frameRate)) {
+ int32_t tmp;
+ if (!msg->findInt32("frame-rate", &tmp)) {
+ return INVALID_OPERATION;
+ }
+ frameRate = (float)tmp;
+ }
+
+ video_def->xFramerate = (OMX_U32)(frameRate * 65536.0f);
+ video_def->eCompressionFormat = OMX_VIDEO_CodingUnused;
+ video_def->eColorFormat = colorFormat;
+
+ err = mOMX->setParameter(
+ mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
+
+ if (err != OK) {
+ ALOGE("[%s] failed to set input port definition parameters.",
+ mComponentName.c_str());
+
+ return err;
+ }
+
+ /* Output port configuration */
+
+ OMX_VIDEO_CODINGTYPE compressionFormat;
+ err = GetVideoCodingTypeFromMime(mime, &compressionFormat);
+
+ if (err != OK) {
+ return err;
+ }
+
+ err = setVideoPortFormatType(
+ kPortIndexOutput, compressionFormat, OMX_COLOR_FormatUnused);
+
+ if (err != OK) {
+ ALOGE("[%s] does not support compression format %d",
+ mComponentName.c_str(), compressionFormat);
+
+ return err;
+ }
+
+ def.nPortIndex = kPortIndexOutput;
+
+ err = mOMX->getParameter(
+ mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
+
+ if (err != OK) {
+ return err;
+ }
+
+ video_def->nFrameWidth = width;
+ video_def->nFrameHeight = height;
+ video_def->xFramerate = 0;
+ video_def->nBitrate = bitrate;
+ video_def->eCompressionFormat = compressionFormat;
+ video_def->eColorFormat = OMX_COLOR_FormatUnused;
+
+ err = mOMX->setParameter(
+ mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
+
+ if (err != OK) {
+ ALOGE("[%s] failed to set output port definition parameters.",
+ mComponentName.c_str());
+
+ return err;
+ }
+
+ switch (compressionFormat) {
+ case OMX_VIDEO_CodingMPEG4:
+ err = setupMPEG4EncoderParameters(msg);
+ break;
+
+ case OMX_VIDEO_CodingH263:
+ err = setupH263EncoderParameters(msg);
+ break;
+
+ case OMX_VIDEO_CodingAVC:
+ err = setupAVCEncoderParameters(msg);
+ break;
+
+ default:
+ break;
+ }
+
+ ALOGI("setupVideoEncoder succeeded");
+
+ return err;
+}
+
+static OMX_U32 setPFramesSpacing(int32_t iFramesInterval, int32_t frameRate) {
+ if (iFramesInterval < 0) {
+ return 0xFFFFFFFF;
+ } else if (iFramesInterval == 0) {
+ return 0;
+ }
+ OMX_U32 ret = frameRate * iFramesInterval;
+ CHECK(ret > 1);
+ return ret;
+}
+
+status_t ACodec::setupMPEG4EncoderParameters(const sp<AMessage> &msg) {
+ int32_t bitrate, iFrameInterval;
+ if (!msg->findInt32("bitrate", &bitrate)
+ || !msg->findInt32("i-frame-interval", &iFrameInterval)) {
+ return INVALID_OPERATION;
+ }
+
+ float frameRate;
+ if (!msg->findFloat("frame-rate", &frameRate)) {
+ int32_t tmp;
+ if (!msg->findInt32("frame-rate", &tmp)) {
+ return INVALID_OPERATION;
+ }
+ frameRate = (float)tmp;
+ }
+
+ OMX_VIDEO_PARAM_MPEG4TYPE mpeg4type;
+ InitOMXParams(&mpeg4type);
+ mpeg4type.nPortIndex = kPortIndexOutput;
+
+ status_t err = mOMX->getParameter(
+ mNode, OMX_IndexParamVideoMpeg4, &mpeg4type, sizeof(mpeg4type));
+
+ if (err != OK) {
+ return err;
+ }
+
+ mpeg4type.nSliceHeaderSpacing = 0;
+ mpeg4type.bSVH = OMX_FALSE;
+ mpeg4type.bGov = OMX_FALSE;
+
+ mpeg4type.nAllowedPictureTypes =
+ OMX_VIDEO_PictureTypeI | OMX_VIDEO_PictureTypeP;
+
+ mpeg4type.nPFrames = setPFramesSpacing(iFrameInterval, frameRate);
+ if (mpeg4type.nPFrames == 0) {
+ mpeg4type.nAllowedPictureTypes = OMX_VIDEO_PictureTypeI;
+ }
+ mpeg4type.nBFrames = 0;
+ mpeg4type.nIDCVLCThreshold = 0;
+ mpeg4type.bACPred = OMX_TRUE;
+ mpeg4type.nMaxPacketSize = 256;
+ mpeg4type.nTimeIncRes = 1000;
+ mpeg4type.nHeaderExtension = 0;
+ mpeg4type.bReversibleVLC = OMX_FALSE;
+
+ int32_t profile;
+ if (msg->findInt32("profile", &profile)) {
+ int32_t level;
+ if (!msg->findInt32("level", &level)) {
+ return INVALID_OPERATION;
+ }
+
+ err = verifySupportForProfileAndLevel(profile, level);
+
+ if (err != OK) {
+ return err;
+ }
+
+ mpeg4type.eProfile = static_cast<OMX_VIDEO_MPEG4PROFILETYPE>(profile);
+ mpeg4type.eLevel = static_cast<OMX_VIDEO_MPEG4LEVELTYPE>(level);
+ }
+
+ err = mOMX->setParameter(
+ mNode, OMX_IndexParamVideoMpeg4, &mpeg4type, sizeof(mpeg4type));
+
+ if (err != OK) {
+ return err;
+ }
+
+ err = configureBitrate(bitrate);
+
+ if (err != OK) {
+ return err;
+ }
+
+ return setupErrorCorrectionParameters();
+}
+
+status_t ACodec::setupH263EncoderParameters(const sp<AMessage> &msg) {
+ int32_t bitrate, iFrameInterval;
+ if (!msg->findInt32("bitrate", &bitrate)
+ || !msg->findInt32("i-frame-interval", &iFrameInterval)) {
+ return INVALID_OPERATION;
+ }
+
+ float frameRate;
+ if (!msg->findFloat("frame-rate", &frameRate)) {
+ int32_t tmp;
+ if (!msg->findInt32("frame-rate", &tmp)) {
+ return INVALID_OPERATION;
+ }
+ frameRate = (float)tmp;
+ }
+
+ OMX_VIDEO_PARAM_H263TYPE h263type;
+ InitOMXParams(&h263type);
+ h263type.nPortIndex = kPortIndexOutput;
+
+ status_t err = mOMX->getParameter(
+ mNode, OMX_IndexParamVideoH263, &h263type, sizeof(h263type));
+
+ if (err != OK) {
+ return err;
+ }
+
+ h263type.nAllowedPictureTypes =
+ OMX_VIDEO_PictureTypeI | OMX_VIDEO_PictureTypeP;
+
+ h263type.nPFrames = setPFramesSpacing(iFrameInterval, frameRate);
+ if (h263type.nPFrames == 0) {
+ h263type.nAllowedPictureTypes = OMX_VIDEO_PictureTypeI;
+ }
+ h263type.nBFrames = 0;
+
+ int32_t profile;
+ if (msg->findInt32("profile", &profile)) {
+ int32_t level;
+ if (!msg->findInt32("level", &level)) {
+ return INVALID_OPERATION;
+ }
+
+ err = verifySupportForProfileAndLevel(profile, level);
+
+ if (err != OK) {
+ return err;
+ }
+
+ h263type.eProfile = static_cast<OMX_VIDEO_H263PROFILETYPE>(profile);
+ h263type.eLevel = static_cast<OMX_VIDEO_H263LEVELTYPE>(level);
+ }
+
+ h263type.bPLUSPTYPEAllowed = OMX_FALSE;
+ h263type.bForceRoundingTypeToZero = OMX_FALSE;
+ h263type.nPictureHeaderRepetition = 0;
+ h263type.nGOBHeaderInterval = 0;
+
+ err = mOMX->setParameter(
+ mNode, OMX_IndexParamVideoH263, &h263type, sizeof(h263type));
+
+ if (err != OK) {
+ return err;
+ }
+
+ err = configureBitrate(bitrate);
+
+ if (err != OK) {
+ return err;
+ }
+
+ return setupErrorCorrectionParameters();
+}
+
+status_t ACodec::setupAVCEncoderParameters(const sp<AMessage> &msg) {
+ int32_t bitrate, iFrameInterval;
+ if (!msg->findInt32("bitrate", &bitrate)
+ || !msg->findInt32("i-frame-interval", &iFrameInterval)) {
+ return INVALID_OPERATION;
+ }
+
+ float frameRate;
+ if (!msg->findFloat("frame-rate", &frameRate)) {
+ int32_t tmp;
+ if (!msg->findInt32("frame-rate", &tmp)) {
+ return INVALID_OPERATION;
+ }
+ frameRate = (float)tmp;
+ }
+
+ OMX_VIDEO_PARAM_AVCTYPE h264type;
+ InitOMXParams(&h264type);
+ h264type.nPortIndex = kPortIndexOutput;
+
+ status_t err = mOMX->getParameter(
+ mNode, OMX_IndexParamVideoAvc, &h264type, sizeof(h264type));
+
+ if (err != OK) {
+ return err;
+ }
+
+ h264type.nAllowedPictureTypes =
+ OMX_VIDEO_PictureTypeI | OMX_VIDEO_PictureTypeP;
+
+ int32_t profile;
+ if (msg->findInt32("profile", &profile)) {
+ int32_t level;
+ if (!msg->findInt32("level", &level)) {
+ return INVALID_OPERATION;
+ }
+
+ err = verifySupportForProfileAndLevel(profile, level);
+
+ if (err != OK) {
+ return err;
+ }
+
+ h264type.eProfile = static_cast<OMX_VIDEO_AVCPROFILETYPE>(profile);
+ h264type.eLevel = static_cast<OMX_VIDEO_AVCLEVELTYPE>(level);
+ }
+
+ // XXX
+ if (!strncmp(mComponentName.c_str(), "OMX.TI.DUCATI1", 14)) {
+ h264type.eProfile = OMX_VIDEO_AVCProfileBaseline;
+ }
+
+ if (h264type.eProfile == OMX_VIDEO_AVCProfileBaseline) {
+ h264type.nSliceHeaderSpacing = 0;
+ h264type.bUseHadamard = OMX_TRUE;
+ h264type.nRefFrames = 1;
+ h264type.nBFrames = 0;
+ h264type.nPFrames = setPFramesSpacing(iFrameInterval, frameRate);
+ if (h264type.nPFrames == 0) {
+ h264type.nAllowedPictureTypes = OMX_VIDEO_PictureTypeI;
+ }
+ h264type.nRefIdx10ActiveMinus1 = 0;
+ h264type.nRefIdx11ActiveMinus1 = 0;
+ h264type.bEntropyCodingCABAC = OMX_FALSE;
+ h264type.bWeightedPPrediction = OMX_FALSE;
+ h264type.bconstIpred = OMX_FALSE;
+ h264type.bDirect8x8Inference = OMX_FALSE;
+ h264type.bDirectSpatialTemporal = OMX_FALSE;
+ h264type.nCabacInitIdc = 0;
+ }
+
+ if (h264type.nBFrames != 0) {
+ h264type.nAllowedPictureTypes |= OMX_VIDEO_PictureTypeB;
+ }
+
+ h264type.bEnableUEP = OMX_FALSE;
+ h264type.bEnableFMO = OMX_FALSE;
+ h264type.bEnableASO = OMX_FALSE;
+ h264type.bEnableRS = OMX_FALSE;
+ h264type.bFrameMBsOnly = OMX_TRUE;
+ h264type.bMBAFF = OMX_FALSE;
+ h264type.eLoopFilterMode = OMX_VIDEO_AVCLoopFilterEnable;
+
+ if (!strcasecmp("OMX.Nvidia.h264.encoder", mComponentName.c_str())) {
+ h264type.eLevel = OMX_VIDEO_AVCLevelMax;
+ }
+
+ err = mOMX->setParameter(
+ mNode, OMX_IndexParamVideoAvc, &h264type, sizeof(h264type));
+
+ if (err != OK) {
+ return err;
+ }
+
+ return configureBitrate(bitrate);
+}
+
+status_t ACodec::verifySupportForProfileAndLevel(
+ int32_t profile, int32_t level) {
+ OMX_VIDEO_PARAM_PROFILELEVELTYPE params;
+ InitOMXParams(&params);
+ params.nPortIndex = kPortIndexOutput;
+
+ for (params.nProfileIndex = 0;; ++params.nProfileIndex) {
+ status_t err = mOMX->getParameter(
+ mNode,
+ OMX_IndexParamVideoProfileLevelQuerySupported,
+ &params,
+ sizeof(params));
+
+ if (err != OK) {
+ return err;
+ }
+
+ int32_t supportedProfile = static_cast<int32_t>(params.eProfile);
+ int32_t supportedLevel = static_cast<int32_t>(params.eLevel);
+
+ if (profile == supportedProfile && level <= supportedLevel) {
+ return OK;
+ }
+ }
+}
+
+status_t ACodec::configureBitrate(int32_t bitrate) {
+ OMX_VIDEO_PARAM_BITRATETYPE bitrateType;
+ InitOMXParams(&bitrateType);
+ bitrateType.nPortIndex = kPortIndexOutput;
+
+ status_t err = mOMX->getParameter(
+ mNode, OMX_IndexParamVideoBitrate,
+ &bitrateType, sizeof(bitrateType));
+
+ if (err != OK) {
+ return err;
+ }
+
+ bitrateType.eControlRate = OMX_Video_ControlRateVariable;
+ bitrateType.nTargetBitrate = bitrate;
+
+ return mOMX->setParameter(
+ mNode, OMX_IndexParamVideoBitrate,
+ &bitrateType, sizeof(bitrateType));
+}
+
+status_t ACodec::setupErrorCorrectionParameters() {
+ OMX_VIDEO_PARAM_ERRORCORRECTIONTYPE errorCorrectionType;
+ InitOMXParams(&errorCorrectionType);
+ errorCorrectionType.nPortIndex = kPortIndexOutput;
+
+ status_t err = mOMX->getParameter(
+ mNode, OMX_IndexParamVideoErrorCorrection,
+ &errorCorrectionType, sizeof(errorCorrectionType));
+
+ if (err != OK) {
+ return OK; // Optional feature. Ignore this failure
+ }
+
+ errorCorrectionType.bEnableHEC = OMX_FALSE;
+ errorCorrectionType.bEnableResync = OMX_TRUE;
+ errorCorrectionType.nResynchMarkerSpacing = 256;
+ errorCorrectionType.bEnableDataPartitioning = OMX_FALSE;
+ errorCorrectionType.bEnableRVLC = OMX_FALSE;
+
+ return mOMX->setParameter(
+ mNode, OMX_IndexParamVideoErrorCorrection,
+ &errorCorrectionType, sizeof(errorCorrectionType));
+}
+
status_t ACodec::setVideoFormatOnPort(
OMX_U32 portIndex,
int32_t width, int32_t height, OMX_VIDEO_CODINGTYPE compressionFormat) {
@@ -1166,6 +1887,9 @@ void ACodec::sendFormatChange() {
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 @@ void ACodec::sendFormatChange() {
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 @@ void ACodec::BaseState::postFillThisBuffer(BufferInfo *info) {
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 @@ void ACodec::BaseState::onInputBufferFilled(const sp<AMessage> &msg) {
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 @@ void ACodec::BaseState::onInputBufferFilled(const sp<AMessage> &msg) {
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 @@ void ACodec::BaseState::onInputBufferFilled(const sp<AMessage> &msg) {
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 @@ void ACodec::BaseState::onInputBufferFilled(const sp<AMessage> &msg) {
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 @@ void ACodec::BaseState::onInputBufferFilled(const sp<AMessage> &msg) {
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 @@ void ACodec::BaseState::onInputBufferFilled(const sp<AMessage> &msg) {
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 @@ bool ACodec::BaseState::onOMXFillBufferDone(
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 @@ bool ACodec::BaseState::onOMXFillBufferDone(
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();
- }
+ info->mStatus = BufferInfo::OWNED_BY_COMPONENT;
+ break;
+ }
- if (mCodec->mNativeWindow == NULL) {
- info->mData->setRange(rangeOffset, rangeLength);
- }
+ if (!mCodec->mIsEncoder && !mCodec->mSentFormat) {
+ mCodec->sendFormatChange();
+ }
- info->mData->meta()->setInt64("timeUs", timeUs);
+ if (mCodec->mNativeWindow == NULL) {
+ info->mData->setRange(rangeOffset, rangeLength);
+ }
- sp<AMessage> notify = mCodec->mNotify->dup();
- notify->setInt32("what", ACodec::kWhatDrainThisBuffer);
- notify->setPointer("buffer-id", info->mBufferID);
- notify->setObject("buffer", info->mData);
+ info->mData->meta()->setInt64("timeUs", timeUs);
- sp<AMessage> reply =
- new AMessage(kWhatOutputBufferDrained, mCodec->id());
+ 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);
- reply->setPointer("buffer-id", info->mBufferID);
+ sp<AMessage> reply =
+ new AMessage(kWhatOutputBufferDrained, mCodec->id());
- notify->setMessage("reply", reply);
+ reply->setPointer("buffer-id", info->mBufferID);
- notify->post();
+ notify->setMessage("reply", reply);
- info->mStatus = BufferInfo::OWNED_BY_DOWNSTREAM;
- }
+ 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 @@ void ACodec::BaseState::onOutputBufferDrained(const sp<AMessage> &msg) {
&& 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 @@ bool ACodec::UninitializedState::onMessageReceived(const sp<AMessage> &msg) {
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 @@ bool ACodec::UninitializedState::onMessageReceived(const sp<AMessage> &msg) {
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();
+ Vector<String8> matchingCodecs;
+
AString mime;
- CHECK(msg->findString("mime", &mime));
- Vector<String8> matchingCodecs;
- OMXCodec::findMatchingCodecs(
- mime.c_str(),
- false, // createEncoder
- NULL, // matchComponentName
- 0, // flags
- &matchingCodecs);
+ 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 @@ void ACodec::UninitializedState::onSetup(
}
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 @@ void ACodec::UninitializedState::onSetup(
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 @@ void ACodec::LoadedToIdleState::stateEntered() {
"(error 0x%08x)",
err);
- mCodec->signalError();
+ mCodec->signalError(OMX_ErrorUndefined, err);
}
}
@@ -2202,7 +3036,7 @@ bool ACodec::OutputPortSettingsChangedState::onOMXEvent(
"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 @@ bool ACodec::OutputPortSettingsChangedState::onOMXEvent(
////////////////////////////////////////////////////////////////////////////////
ACodec::ExecutingToIdleState::ExecutingToIdleState(ACodec *codec)
- : BaseState(codec) {
+ : BaseState(codec),
+ mComponentNowIdle(false) {
}
bool ACodec::ExecutingToIdleState::onMessageReceived(const sp<AMessage> &msg) {
@@ -2274,6 +3109,7 @@ bool ACodec::ExecutingToIdleState::onMessageReceived(const sp<AMessage> &msg) {
void ACodec::ExecutingToIdleState::stateEntered() {
ALOGV("[%s] Now Executing->Idle", mCodec->mComponentName.c_str());
+ mComponentNowIdle = false;
mCodec->mSentFormat = false;
}
@@ -2285,6 +3121,8 @@ bool ACodec::ExecutingToIdleState::onOMXEvent(
CHECK_EQ(data1, (OMX_U32)OMX_CommandStateSet);
CHECK_EQ(data2, (OMX_U32)OMX_StateIdle);
+ mComponentNowIdle = true;
+
changeStateIfWeOwnAllBuffers();
return true;
@@ -2303,7 +3141,7 @@ bool ACodec::ExecutingToIdleState::onOMXEvent(
}
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 3f9ba47e1345..cfb1e29aa1bf 100644
--- a/media/libstagefright/Android.mk
+++ b/media/libstagefright/Android.mk
@@ -29,12 +29,14 @@ LOCAL_SRC_FILES:= \
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 @@ LOCAL_C_INCLUDES:= \
$(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 @@ LOCAL_STATIC_LIBRARIES := \
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 0b4ecbea4e6c..f70237628291 100644
--- a/media/libstagefright/MPEG2TSWriter.cpp
+++ b/media/libstagefright/MPEG2TSWriter.cpp
@@ -244,7 +244,7 @@ void MPEG2TSWriter::SourceInfo::extractCodecSpecificData() {
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 @@ void MPEG2TSWriter::SourceInfo::postAVCFrame(MediaBuffer *buffer) {
copy->meta()->setInt32("isSync", true);
}
- notify->setObject("buffer", copy);
+ notify->setBuffer("buffer", copy);
notify->post();
}
@@ -351,7 +351,7 @@ bool MPEG2TSWriter::SourceInfo::flushAACFrames() {
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 @@ void MPEG2TSWriter::onMessageReceived(const sp<AMessage> &msg) {
++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 000000000000..4acbdbe8b0f4
--- /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 000000000000..afd476356047
--- /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 7a805aa73afc..7cdb793b594e 100644
--- a/media/libstagefright/OMXClient.cpp
+++ b/media/libstagefright/OMXClient.cpp
@@ -335,6 +335,10 @@ status_t OMXClient::connect() {
}
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 470f7502be66..1325462714bc 100755
--- a/media/libstagefright/OMXCodec.cpp
+++ b/media/libstagefright/OMXCodec.cpp
@@ -1541,6 +1541,8 @@ void OMXCodec::setComponentRole(
"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 @@ status_t OMXCodec::setAACFormat(int32_t numChannels, int32_t sampleRate, int32_t
//////////////// 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 0a6776ef5130..9a001865e50b 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 @@ void AMessage::setString(
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 @@ bool AMessage::findObject(const char *name, sp<RefBase> *obj) const {
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 @@ void AMessage::writeToParcel(Parcel *parcel) const {
}
}
+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 9d72b1fd9a85..fb8abc580c17 100644
--- a/media/libstagefright/rtsp/AAMRAssembler.cpp
+++ b/media/libstagefright/rtsp/AAMRAssembler.cpp
@@ -211,7 +211,7 @@ ARTPAssembler::AssemblyStatus AAMRAssembler::addPacket(
}
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 ed8b1df2dda8..7ea132e54cf4 100644
--- a/media/libstagefright/rtsp/AAVCAssembler.cpp
+++ b/media/libstagefright/rtsp/AAVCAssembler.cpp
@@ -345,7 +345,7 @@ void AAVCAssembler::submitAccessUnit() {
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 498295c46e1b..ded70fa17439 100644
--- a/media/libstagefright/rtsp/AH263Assembler.cpp
+++ b/media/libstagefright/rtsp/AH263Assembler.cpp
@@ -166,7 +166,7 @@ void AH263Assembler::submitAccessUnit() {
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 b0c7007c3737..24c2f30209ef 100644
--- a/media/libstagefright/rtsp/AMPEG4AudioAssembler.cpp
+++ b/media/libstagefright/rtsp/AMPEG4AudioAssembler.cpp
@@ -571,7 +571,7 @@ void AMPEG4AudioAssembler::submitAccessUnit() {
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 2f2e2c26e395..687d72bbda56 100644
--- a/media/libstagefright/rtsp/AMPEG4ElementaryAssembler.cpp
+++ b/media/libstagefright/rtsp/AMPEG4ElementaryAssembler.cpp
@@ -368,7 +368,7 @@ void AMPEG4ElementaryAssembler::submitAccessUnit() {
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 8c9dd8d3d5ce..44988a34733f 100644
--- a/media/libstagefright/rtsp/ARTPConnection.cpp
+++ b/media/libstagefright/rtsp/ARTPConnection.cpp
@@ -639,7 +639,7 @@ sp<ARTPSource> ARTPConnection::findSource(StreamInfo *info, uint32_t srcId) {
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 @@ void ARTPConnection::onInjectPacket(const sp<AMessage> &msg) {
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 7a05b888f630..ba4e33c813db 100644
--- a/media/libstagefright/rtsp/ARTPSession.cpp
+++ b/media/libstagefright/rtsp/ARTPSession.cpp
@@ -145,10 +145,8 @@ void ARTPSession::onMessageReceived(const sp<AMessage> &msg) {
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 80a010e2f466..539a88823cab 100644
--- a/media/libstagefright/rtsp/ARTSPConnection.cpp
+++ b/media/libstagefright/rtsp/ARTSPConnection.cpp
@@ -612,7 +612,7 @@ bool ARTSPConnection::receiveRTSPReponse() {
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 98bee82e23a0..0da5dd250e9b 100644
--- a/media/libstagefright/rtsp/ARawAudioAssembler.cpp
+++ b/media/libstagefright/rtsp/ARawAudioAssembler.cpp
@@ -94,7 +94,7 @@ ARTPAssembler::AssemblyStatus ARawAudioAssembler::addPacket(
}
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 9a7dd704807a..deee30f14600 100644
--- a/media/libstagefright/rtsp/MyHandler.h
+++ b/media/libstagefright/rtsp/MyHandler.h
@@ -857,10 +857,8 @@ struct MyHandler : public AHandler {
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 @@ struct MyHandler : public AHandler {
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 @@ private:
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/libstagefright/timedtext/Android.mk b/media/libstagefright/timedtext/Android.mk
index 8b23dee0c0c7..dde206673e9a 100644
--- a/media/libstagefright/timedtext/Android.mk
+++ b/media/libstagefright/timedtext/Android.mk
@@ -4,7 +4,7 @@ include $(CLEAR_VARS)
LOCAL_SRC_FILES:= \
TextDescriptions.cpp \
TimedTextDriver.cpp \
- TimedTextInBandSource.cpp \
+ TimedText3GPPSource.cpp \
TimedTextSource.cpp \
TimedTextSRTSource.cpp \
TimedTextPlayer.cpp
@@ -12,8 +12,7 @@ LOCAL_SRC_FILES:= \
LOCAL_CFLAGS += -Wno-multichar
LOCAL_C_INCLUDES:= \
$(JNI_H_INCLUDE) \
- $(TOP)/frameworks/base/media/libstagefright \
- $(TOP)/frameworks/base/include/media/stagefright/openmax
+ $(TOP)/frameworks/base/media/libstagefright
LOCAL_MODULE:= libstagefright_timedtext
diff --git a/media/libstagefright/timedtext/TimedTextInBandSource.cpp b/media/libstagefright/timedtext/TimedText3GPPSource.cpp
index afb73fb50313..4a3bfd323e74 100644
--- a/media/libstagefright/timedtext/TimedTextInBandSource.cpp
+++ b/media/libstagefright/timedtext/TimedText3GPPSource.cpp
@@ -15,7 +15,7 @@
*/
//#define LOG_NDEBUG 0
-#define LOG_TAG "TimedTextInBandSource"
+#define LOG_TAG "TimedText3GPPSource"
#include <utils/Log.h>
#include <binder/Parcel.h>
@@ -26,19 +26,19 @@
#include <media/stagefright/MediaSource.h>
#include <media/stagefright/MetaData.h>
-#include "TimedTextInBandSource.h"
+#include "TimedText3GPPSource.h"
#include "TextDescriptions.h"
namespace android {
-TimedTextInBandSource::TimedTextInBandSource(const sp<MediaSource>& mediaSource)
+TimedText3GPPSource::TimedText3GPPSource(const sp<MediaSource>& mediaSource)
: mSource(mediaSource) {
}
-TimedTextInBandSource::~TimedTextInBandSource() {
+TimedText3GPPSource::~TimedText3GPPSource() {
}
-status_t TimedTextInBandSource::read(
+status_t TimedText3GPPSource::read(
int64_t *timeUs, Parcel *parcel, const MediaSource::ReadOptions *options) {
MediaBuffer *textBuffer = NULL;
status_t err = mSource->read(&textBuffer, options);
@@ -60,7 +60,7 @@ status_t TimedTextInBandSource::read(
// text style for the string of text. These descriptions are present only
// if they are needed. This method is used to extract the modifier
// description and append it at the end of the text.
-status_t TimedTextInBandSource::extractAndAppendLocalDescriptions(
+status_t TimedText3GPPSource::extractAndAppendLocalDescriptions(
int64_t timeUs, const MediaBuffer *textBuffer, Parcel *parcel) {
const void *data;
size_t size = 0;
@@ -68,51 +68,46 @@ status_t TimedTextInBandSource::extractAndAppendLocalDescriptions(
const char *mime;
CHECK(mSource->getFormat()->findCString(kKeyMIMEType, &mime));
+ CHECK(strcasecmp(mime, MEDIA_MIMETYPE_TEXT_3GPP) == 0);
- if (strcasecmp(mime, MEDIA_MIMETYPE_TEXT_3GPP) == 0) {
- data = textBuffer->data();
- size = textBuffer->size();
-
- if (size > 0) {
- parcel->freeData();
- flag |= TextDescriptions::IN_BAND_TEXT_3GPP;
- return TextDescriptions::getParcelOfDescriptions(
- (const uint8_t *)data, size, flag, timeUs / 1000, parcel);
- }
- return OK;
+ data = textBuffer->data();
+ size = textBuffer->size();
+
+ if (size > 0) {
+ parcel->freeData();
+ flag |= TextDescriptions::IN_BAND_TEXT_3GPP;
+ return TextDescriptions::getParcelOfDescriptions(
+ (const uint8_t *)data, size, flag, timeUs / 1000, parcel);
}
- return ERROR_UNSUPPORTED;
+ return OK;
}
// To extract and send the global text descriptions for all the text samples
// in the text track or text file.
// TODO: send error message to application via notifyListener()...?
-status_t TimedTextInBandSource::extractGlobalDescriptions(Parcel *parcel) {
+status_t TimedText3GPPSource::extractGlobalDescriptions(Parcel *parcel) {
const void *data;
size_t size = 0;
int32_t flag = TextDescriptions::GLOBAL_DESCRIPTIONS;
const char *mime;
CHECK(mSource->getFormat()->findCString(kKeyMIMEType, &mime));
+ CHECK(strcasecmp(mime, MEDIA_MIMETYPE_TEXT_3GPP) == 0);
+
+ uint32_t type;
+ // get the 'tx3g' box content. This box contains the text descriptions
+ // used to render the text track
+ if (!mSource->getFormat()->findData(
+ kKeyTextFormatData, &type, &data, &size)) {
+ return ERROR_MALFORMED;
+ }
- // support 3GPP only for now
- if (strcasecmp(mime, MEDIA_MIMETYPE_TEXT_3GPP) == 0) {
- uint32_t type;
- // get the 'tx3g' box content. This box contains the text descriptions
- // used to render the text track
- if (!mSource->getFormat()->findData(
- kKeyTextFormatData, &type, &data, &size)) {
- return ERROR_MALFORMED;
- }
-
- if (size > 0) {
- flag |= TextDescriptions::IN_BAND_TEXT_3GPP;
- return TextDescriptions::getParcelOfDescriptions(
- (const uint8_t *)data, size, flag, 0, parcel);
- }
- return OK;
+ if (size > 0) {
+ flag |= TextDescriptions::IN_BAND_TEXT_3GPP;
+ return TextDescriptions::getParcelOfDescriptions(
+ (const uint8_t *)data, size, flag, 0, parcel);
}
- return ERROR_UNSUPPORTED;
+ return OK;
}
} // namespace android
diff --git a/media/libstagefright/timedtext/TimedTextInBandSource.h b/media/libstagefright/timedtext/TimedText3GPPSource.h
index 26e5737f06ee..cb7e47c9def3 100644
--- a/media/libstagefright/timedtext/TimedTextInBandSource.h
+++ b/media/libstagefright/timedtext/TimedText3GPPSource.h
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-#ifndef TIMED_TEXT_IN_BAND_SOURCE_H_
-#define TIMED_TEXT_IN_BAND_SOURCE_H_
+#ifndef TIMED_TEXT_3GPP_SOURCE_H_
+#define TIMED_TEXT_3GPP_SOURCE_H_
#include <media/stagefright/MediaErrors.h>
#include <media/stagefright/MediaSource.h>
@@ -27,9 +27,9 @@ namespace android {
class MediaBuffer;
class Parcel;
-class TimedTextInBandSource : public TimedTextSource {
+class TimedText3GPPSource : public TimedTextSource {
public:
- TimedTextInBandSource(const sp<MediaSource>& mediaSource);
+ TimedText3GPPSource(const sp<MediaSource>& mediaSource);
virtual status_t start() { return mSource->start(); }
virtual status_t stop() { return mSource->stop(); }
virtual status_t read(
@@ -39,7 +39,7 @@ class TimedTextInBandSource : public TimedTextSource {
virtual status_t extractGlobalDescriptions(Parcel *parcel);
protected:
- virtual ~TimedTextInBandSource();
+ virtual ~TimedText3GPPSource();
private:
sp<MediaSource> mSource;
@@ -47,9 +47,9 @@ class TimedTextInBandSource : public TimedTextSource {
status_t extractAndAppendLocalDescriptions(
int64_t timeUs, const MediaBuffer *textBuffer, Parcel *parcel);
- DISALLOW_EVIL_CONSTRUCTORS(TimedTextInBandSource);
+ DISALLOW_EVIL_CONSTRUCTORS(TimedText3GPPSource);
};
} // namespace android
-#endif // TIMED_TEXT_IN_BAND_SOURCE_H_
+#endif // TIMED_TEXT_3GPP_SOURCE_H_
diff --git a/media/libstagefright/timedtext/TimedTextSource.cpp b/media/libstagefright/timedtext/TimedTextSource.cpp
index 9efe67c45af0..ffbe1c3bca41 100644
--- a/media/libstagefright/timedtext/TimedTextSource.cpp
+++ b/media/libstagefright/timedtext/TimedTextSource.cpp
@@ -18,12 +18,15 @@
#define LOG_TAG "TimedTextSource"
#include <utils/Log.h>
+#include <media/stagefright/foundation/ADebug.h> // CHECK_XX macro
#include <media/stagefright/DataSource.h>
+#include <media/stagefright/MediaDefs.h> // for MEDIA_MIMETYPE_xxx
#include <media/stagefright/MediaSource.h>
+#include <media/stagefright/MetaData.h>
#include "TimedTextSource.h"
-#include "TimedTextInBandSource.h"
+#include "TimedText3GPPSource.h"
#include "TimedTextSRTSource.h"
namespace android {
@@ -31,7 +34,13 @@ namespace android {
// static
sp<TimedTextSource> TimedTextSource::CreateTimedTextSource(
const sp<MediaSource>& mediaSource) {
- return new TimedTextInBandSource(mediaSource);
+ const char *mime;
+ CHECK(mediaSource->getFormat()->findCString(kKeyMIMEType, &mime));
+ if (strcasecmp(mime, MEDIA_MIMETYPE_TEXT_3GPP) == 0) {
+ return new TimedText3GPPSource(mediaSource);
+ }
+ ALOGE("Unsupported mime type for subtitle. : %s", mime);
+ return NULL;
}
// static
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index c12a4b7e5917..8f35afb8ea09 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 @@ public class PhoneWindowManager implements WindowManagerPolicy {
"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/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
index 455325af5156..c99aa026cf52 100644
--- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -1129,7 +1129,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
public float findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId,
long accessibilityNodeId, int interactionId,
- IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
+ IAccessibilityInteractionConnectionCallback callback, long interrogatingTid,
+ int prefetchFlags)
throws RemoteException {
final int resolvedWindowId = resolveAccessibilityWindowId(accessibilityWindowId);
IAccessibilityInteractionConnection connection = null;
@@ -1150,7 +1151,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
final long identityToken = Binder.clearCallingIdentity();
try {
connection.findAccessibilityNodeInfoByAccessibilityId(accessibilityNodeId,
- interactionId, callback, interrogatingPid, interrogatingTid);
+ interactionId, callback, prefetchFlags, interrogatingPid, interrogatingTid);
} catch (RemoteException re) {
if (DEBUG) {
Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfoByAccessibilityId()");
diff --git a/services/java/com/android/server/wm/DimAnimator.java b/services/java/com/android/server/wm/DimAnimator.java
index a3293e8bbe87..a9d4e01aedb1 100644
--- a/services/java/com/android/server/wm/DimAnimator.java
+++ b/services/java/com/android/server/wm/DimAnimator.java
@@ -180,7 +180,9 @@ class DimAnimator {
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 dafc6135229d..21cb3e8dd975 100644
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -8570,6 +8570,11 @@ public class WindowManagerService extends IWindowManager.Stub
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);
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/PathsCacheActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/PathsCacheActivity.java
index b8ad823bf73a..9ab2a860f7cc 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/PathsCacheActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/PathsCacheActivity.java
@@ -92,19 +92,24 @@ public class PathsCacheActivity extends Activity {
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();
-//
+ for (int i = 0; i < mRandom.nextInt(20); i++) {
+ Path path = makePath();
+ int r = mRandom.nextInt(10);
+ if (r == 5 || r == 3) {
+ mPathList.add(path);
+ }
+
+ canvas.save();
+ canvas.translate(450.0f + mRandom.nextInt(200), mRandom.nextInt(200));
+ canvas.drawPath(path, mMediumPaint);
+ canvas.restore();
+ }
+
+ int r = mRandom.nextInt(100);
+ if (r == 50) {
+ mPathList.clear();
+ }
+
invalidate();
}
}
diff --git a/tests/RenderScriptTests/SceneGraph/res/raw/unit_obj.a3d b/tests/RenderScriptTests/SceneGraph/res/raw/unit_obj.a3d
new file mode 100644
index 000000000000..56eff046c696
--- /dev/null
+++ b/tests/RenderScriptTests/SceneGraph/res/raw/unit_obj.a3d
Binary files differ
diff --git a/tests/RenderScriptTests/SceneGraph/src/com/android/testapp/SimpleAppRS.java b/tests/RenderScriptTests/SceneGraph/src/com/android/testapp/SimpleAppRS.java
index 621bfa364bac..fff6f3407a92 100644
--- a/tests/RenderScriptTests/SceneGraph/src/com/android/testapp/SimpleAppRS.java
+++ b/tests/RenderScriptTests/SceneGraph/src/com/android/testapp/SimpleAppRS.java
@@ -34,9 +34,6 @@ 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;
@@ -44,6 +41,8 @@ public class SimpleAppRS {
Scene mScene;
Mesh mSimpleMesh;
+ Mesh mSphereMesh;
+ Mesh mCubeMesh;
public void init(RenderScriptGL rs, Resources res, int width, int height) {
mRS = rs;
@@ -54,7 +53,9 @@ public class SimpleAppRS {
mScene = new Scene();
setupGeometry();
- setupRenderables();
+ setupColoredQuad();
+ setupTexturedQuad();
+ setupShadedGeometry();
setupCamera();
setupRenderPass();
@@ -68,6 +69,7 @@ public class SimpleAppRS {
Mesh.TriangleMeshBuilder tmb = new Mesh.TriangleMeshBuilder(mRS, 3,
Mesh.TriangleMeshBuilder.TEXTURE_0);
+ // Create four vertices with texture coordinates
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);
@@ -76,36 +78,110 @@ public class SimpleAppRS {
tmb.addTriangle(0, 1, 2);
tmb.addTriangle(2, 3, 0);
mSimpleMesh = tmb.create(true);
+
+ // Load a file that constains two pieces of geometry, a sphere and a cube
+ FileA3D model = FileA3D.createFromResource(mRS, mRes, R.raw.unit_obj);
+ for (int i = 0; i < model.getIndexEntryCount(); i ++) {
+ FileA3D.IndexEntry entry = model.getIndexEntry(i);
+ if (entry != null && entry.getName().equals("CubeMesh")) {
+ mCubeMesh = entry.getMesh();
+ } else if (entry != null && entry.getName().equals("SphereMesh")) {
+ mSphereMesh = entry.getMesh();
+ }
+ }
}
- private void setupRenderables() {
+ private void setupColoredQuad() {
// 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);
+ // Our shader has a constant input called "color"
+ // This tells the scenegraph to assign the following float3 to that input
quad.appendSourceParams(new Float4Param("color", 0.2f, 0.3f, 0.4f));
quad.setRenderState(colorRS);
+ }
+
+ private void setupTexturedQuad() {
+ // Built-in shader that provides position, texcoord and normal
+ VertexShader genericV = SceneManager.getDefaultVS();
+ // Built-in shader that displays a texture
+ FragmentShader textureF = SceneManager.getTextureFS();
+ // We want to use transparency based on the alpha channel of the texture
+ ProgramStore alphaBlend = ProgramStore.BLEND_ALPHA_DEPTH_TEST(mRS);
+ RenderState texRS = new RenderState(genericV, textureF, alphaBlend, null);
// Draw a textured quad
- quad = mScene.appendNewRenderable();
+ Renderable 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);
+ // Our fragment shader has a constant texture input called "color"
+ // This will assign an icon from drawables to that input
quad.appendSourceParams(new TextureParam("color", new Texture2D(R.drawable.icon)));
quad.setRenderState(texRS);
}
+ private FragmentShader createLambertShader() {
+ // Describe what constant inputs our shader wants
+ Element.Builder b = new Element.Builder(mRS);
+ b.add(Element.F32_4(mRS), "cameraPos");
+
+ // Create a shader from a text file in resources
+ FragmentShader.Builder fb = new FragmentShader.Builder(mRS);
+ // Tell the shader what constants we want
+ fb.setShaderConst(new Type.Builder(mRS, b.create()).setX(1).create());
+ // Shader code location
+ fb.setShader(mRes, R.raw.diffuse);
+ // We want a texture called diffuse on our shader
+ fb.addTexture(TextureType.TEXTURE_2D, "diffuse");
+ FragmentShader shader = fb.create();
+ mScene.appendShader(shader);
+ return shader;
+ }
+
+ private void setupShadedGeometry() {
+ // Built-in shader that provides position, texcoord and normal
+ VertexShader genericV = SceneManager.getDefaultVS();
+ // Custom shader
+ FragmentShader diffuseF = createLambertShader();
+ RenderState diffuseRS = new RenderState(genericV, diffuseF, null, null);
+
+ // Draw a sphere
+ Renderable sphere = mScene.appendNewRenderable();
+ // Use the sphere geometry loaded earlier
+ sphere.setMesh(mSphereMesh);
+ // Make a transform to position the sphere
+ CompoundTransform t = mScene.appendNewCompoundTransform();
+ t.addTranslate("position", new Float3(-1, 2, 3));
+ t.addScale("scale", new Float3(1.4f, 1.4f, 1.4f));
+ sphere.setTransform(t);
+ // Tell the renderable which texture to use when we draw
+ // This will mean a texture param in the shader called "diffuse"
+ // will be assigned a texture called red.jpg
+ sphere.appendSourceParams(new TextureParam("diffuse", new Texture2D("", "red.jpg")));
+ sphere.setRenderState(diffuseRS);
+
+ // Draw a cube
+ Renderable cube = mScene.appendNewRenderable();
+ cube.setMesh(mCubeMesh);
+ t = mScene.appendNewCompoundTransform();
+ t.addTranslate("position", new Float3(-2, -2.1f, 0));
+ t.addRotate("rotateX", new Float3(1, 0, 0), 30);
+ t.addRotate("rotateY", new Float3(0, 1, 0), 30);
+ t.addScale("scale", new Float3(2, 2, 2));
+ cube.setTransform(t);
+ cube.appendSourceParams(new TextureParam("diffuse", new Texture2D("", "orange.jpg")));
+ cube.setRenderState(diffuseRS);
+ }
+
private void setupCamera() {
Camera camera = mScene.appendNewCamera();
camera.setFar(200);
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 c7bd80994603..6f5622363de8 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 @@ public class RSTestCore {
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 @@ public class RSTestCore {
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 1d2555ec4b07..04e92700f5de 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 @@ public class UT_foreach extends UnitTest {
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 000000000000..c660fc53fbcc
--- /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 3ba3eef1bbc8..ac527b597432 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 @@ static bool test_foreach_output() {
}
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 000000000000..33944aa15510
--- /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/tools/aapt/XMLNode.cpp b/tools/aapt/XMLNode.cpp
index 95a68d174580..8d7acee54461 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 @@ bool isWhitespace(const char16_t* str)
}
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 @@ status_t XMLNode::assignResourceIds(const sp<AaptAssets>& assets,
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 0a3cdc66f032..6ac5b02916ae 100644
--- a/tools/layoutlib/bridge/src/android/util/BridgeXmlPullAttributes.java
+++ b/tools/layoutlib/bridge/src/android/util/BridgeXmlPullAttributes.java
@@ -18,6 +18,7 @@ package android.util;
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 com.android.resources.ResourceType;
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 @@ public class BridgeXmlPullAttributes extends XmlPullAttributes {
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;
+ }
+
+ @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;
}
- /*
- * (non-Javadoc)
- * @see android.util.XmlPullAttributes#getAttributeResourceValue(java.lang.String, java.lang.String, int)
- */
@Override
public int getAttributeResourceValue(String namespace, String attribute, int defaultValue) {
String value = getAttributeValue(namespace, attribute);
@@ -102,12 +119,151 @@ public class BridgeXmlPullAttributes extends XmlPullAttributes {
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 @@ public class BridgeXmlPullAttributes extends XmlPullAttributes {
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 33bf7bc785e3..ff882099b177 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.DrawableParams;
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 @@ public final class Bridge extends com.android.ide.common.rendering.api.Bridge {
if (fontLoader != null) {
Typeface_Delegate.init(fontLoader);
} else {
+ log.error(LayoutLog.TAG_BROKEN,
+ "Failed create FontLoader in layout lib.", null);
return false;
}