diff options
24 files changed, 700 insertions, 320 deletions
diff --git a/api/current.xml b/api/current.xml index 57a76de65994..eb021b90e643 100644 --- a/api/current.xml +++ b/api/current.xml @@ -198616,6 +198616,17 @@ <exception name="IOException" type="java.io.IOException"> </exception> </method> +<method name="isLenient" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="nextBoolean" return="boolean" abstract="false" @@ -198746,21 +198757,6 @@ <exception name="IOException" type="java.io.IOException"> </exception> </method> -<method name="syntaxError" - return="java.io.IOException" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -<parameter name="message" type="java.lang.String"> -</parameter> -<exception name="IOException" type="java.io.IOException"> -</exception> -</method> </class> <class name="JsonToken" extends="java.lang.Enum" @@ -198893,6 +198889,17 @@ <exception name="IOException" type="java.io.IOException"> </exception> </method> +<method name="isLenient" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="name" return="android.util.JsonWriter" abstract="false" @@ -198934,6 +198941,19 @@ <parameter name="indent" type="java.lang.String"> </parameter> </method> +<method name="setLenient" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="lenient" type="boolean"> +</parameter> +</method> <method name="value" return="android.util.JsonWriter" abstract="false" @@ -198994,6 +199014,21 @@ <exception name="IOException" type="java.io.IOException"> </exception> </method> +<method name="value" + return="android.util.JsonWriter" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="value" type="java.lang.Number"> +</parameter> +<exception name="IOException" type="java.io.IOException"> +</exception> +</method> </class> <class name="Log" extends="java.lang.Object" @@ -199373,6 +199408,25 @@ </parameter> </method> </class> +<class name="MalformedJsonException" + extends="java.io.IOException" + abstract="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +<constructor name="MalformedJsonException" + type="android.util.MalformedJsonException" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="message" type="java.lang.String"> +</parameter> +</constructor> +</class> <class name="MonthDisplayHelper" extends="java.lang.Object" abstract="false" @@ -258003,7 +258057,7 @@ deprecated="not deprecated" visibility="public" > -<parameter name="t" type="T"> +<parameter name="arg0" type="T"> </parameter> </method> </interface> diff --git a/core/java/android/util/JsonReader.java b/core/java/android/util/JsonReader.java index 80255452ca6e..8f44895bab01 100644 --- a/core/java/android/util/JsonReader.java +++ b/core/java/android/util/JsonReader.java @@ -16,6 +16,7 @@ package android.util; +import java.io.EOFException; import java.io.IOException; import java.io.Reader; import java.io.Closeable; @@ -249,6 +250,13 @@ public final class JsonReader implements Closeable { } /** + * Returns true if this parser is liberal in what it accepts. + */ + public boolean isLenient() { + return lenient; + } + + /** * Consumes the next token from the JSON stream and asserts that it is the * beginning of a new array. */ @@ -311,7 +319,7 @@ public final class JsonReader implements Closeable { case EMPTY_DOCUMENT: replaceTop(JsonScope.NONEMPTY_DOCUMENT); JsonToken firstToken = nextValue(); - if (token != JsonToken.BEGIN_ARRAY && token != JsonToken.BEGIN_OBJECT) { + if (!lenient && token != JsonToken.BEGIN_ARRAY && token != JsonToken.BEGIN_OBJECT) { throw new IOException( "Expected JSON document to start with '[' or '{' but was " + token); } @@ -327,7 +335,15 @@ public final class JsonReader implements Closeable { case NONEMPTY_OBJECT: return nextInObject(false); case NONEMPTY_DOCUMENT: - return token = JsonToken.END_DOCUMENT; + try { + JsonToken token = nextValue(); + if (lenient) { + return token; + } + throw syntaxError("Expected EOF"); + } catch (EOFException e) { + return token = JsonToken.END_DOCUMENT; // TODO: avoid throwing here? + } case CLOSED: throw new IllegalStateException("JsonReader is closed"); default: @@ -758,7 +774,7 @@ public final class JsonReader implements Closeable { } } - throw syntaxError("End of input"); + throw new EOFException("End of input"); } private void checkLenient() throws IOException { @@ -1030,8 +1046,6 @@ public final class JsonReader implements Closeable { * form -12.34e+56. Fractional and exponential parts are optional. Leading * zeroes are not allowed in the value or exponential part, but are allowed * in the fraction. - * - * <p>This has a side effect of setting isInteger. */ private JsonToken decodeNumber(char[] chars, int offset, int length) { int i = offset; @@ -1085,8 +1099,8 @@ public final class JsonReader implements Closeable { * Throws a new IO exception with the given message and a context snippet * with this reader's content. */ - public IOException syntaxError(String message) throws IOException { - throw new JsonSyntaxException(message + " near " + getSnippet()); + private IOException syntaxError(String message) throws IOException { + throw new MalformedJsonException(message + " near " + getSnippet()); } private CharSequence getSnippet() { @@ -1097,10 +1111,4 @@ public final class JsonReader implements Closeable { snippet.append(buffer, pos, afterPos); return snippet; } - - private static class JsonSyntaxException extends IOException { - private JsonSyntaxException(String s) { - super(s); - } - } } diff --git a/core/java/android/util/JsonWriter.java b/core/java/android/util/JsonWriter.java index 89cad79eb858..47e84c51dd25 100644 --- a/core/java/android/util/JsonWriter.java +++ b/core/java/android/util/JsonWriter.java @@ -138,6 +138,8 @@ public final class JsonWriter implements Closeable { */ private String separator = ":"; + private boolean lenient; + /** * Creates a new instance that writes a JSON-encoded stream to {@code out}. * For best performance, ensure {@link Writer} is buffered; wrapping in @@ -169,6 +171,29 @@ public final class JsonWriter implements Closeable { } /** + * Configure this writer to relax its syntax rules. By default, this writer + * only emits well-formed JSON as specified by <a + * href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>. Setting the writer + * to lenient permits the following: + * <ul> + * <li>Top-level values of any type. With strict writing, the top-level + * value must be an object or an array. + * <li>Numbers may be {@link Double#isNaN() NaNs} or {@link + * Double#isInfinite() infinities}. + * </ul> + */ + public void setLenient(boolean lenient) { + this.lenient = lenient; + } + + /** + * Returns true if this writer has relaxed syntax rules. + */ + public boolean isLenient() { + return lenient; + } + + /** * Begins encoding a new array. Each call to this method must be paired with * a call to {@link #endArray}. * @@ -306,11 +331,11 @@ public final class JsonWriter implements Closeable { * Encodes {@code value}. * * @param value a finite value. May not be {@link Double#isNaN() NaNs} or - * {@link Double#isInfinite() infinities}. + * {@link Double#isInfinite() infinities} unless this writer is lenient. * @return this writer. */ public JsonWriter value(double value) throws IOException { - if (Double.isNaN(value) || Double.isInfinite(value)) { + if (!lenient && (Double.isNaN(value) || Double.isInfinite(value))) { throw new IllegalArgumentException("Numeric values must be finite, but was " + value); } beforeValue(false); @@ -330,6 +355,28 @@ public final class JsonWriter implements Closeable { } /** + * Encodes {@code value}. + * + * @param value a finite value. May not be {@link Double#isNaN() NaNs} or + * {@link Double#isInfinite() infinities} unless this writer is lenient. + * @return this writer. + */ + public JsonWriter value(Number value) throws IOException { + if (value == null) { + return nullValue(); + } + + String string = value.toString(); + if (!lenient && + (string.equals("-Infinity") || string.equals("Infinity") || string.equals("NaN"))) { + throw new IllegalArgumentException("Numeric values must be finite, but was " + value); + } + beforeValue(false); + out.append(string); + return this; + } + + /** * Ensures all buffered data is written to the underlying {@link Writer} * and flushes that writer. */ @@ -364,7 +411,6 @@ public final class JsonWriter implements Closeable { switch (c) { case '"': case '\\': - case '/': out.write('\\'); out.write(c); break; @@ -439,7 +485,7 @@ public final class JsonWriter implements Closeable { private void beforeValue(boolean root) throws IOException { switch (peek()) { case EMPTY_DOCUMENT: // first in document - if (!root) { + if (!lenient && !root) { throw new IllegalStateException( "JSON must start with an array or an object."); } diff --git a/core/java/android/util/MalformedJsonException.java b/core/java/android/util/MalformedJsonException.java new file mode 100644 index 000000000000..63c19ff86cd4 --- /dev/null +++ b/core/java/android/util/MalformedJsonException.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2011 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.util; + +import java.io.IOException; + +/** + * Thrown when a reader encounters malformed JSON. Some syntax errors can be + * ignored by calling {@link JsonReader#setLenient(boolean)}. + */ +public final class MalformedJsonException extends IOException { + private static final long serialVersionUID = 1L; + + public MalformedJsonException(String message) { + super(message); + } +} diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 61f0bce4b90a..f98ad74de7eb 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -2961,12 +2961,12 @@ public class WebView extends AbsoluteLayout * @return boolean True if the find dialog is shown, false otherwise. */ public boolean showFindDialog(String text, boolean showIme) { - mFindCallback = new FindActionModeCallback(mContext); - if (startActionMode(mFindCallback) == null) { + FindActionModeCallback callback = new FindActionModeCallback(mContext); + if (startActionMode(callback) == null) { // Could not start the action mode, so end Find on page - mFindCallback = null; return false; } + mFindCallback = callback; setFindIsUp(true); mFindCallback.setWebView(this); if (showIme) { @@ -4564,14 +4564,6 @@ public class WebView extends AbsoluteLayout return true; } - if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT - || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) { - if (!nativePageShouldHandleShiftAndArrows() && !nativeCursorWantsKeyEvents() - && !mSelectingText) { - setUpSelect(); - } - } - if (keyCode == KeyEvent.KEYCODE_PAGE_UP) { if (event.hasNoModifiers()) { pageUp(false); @@ -4662,12 +4654,6 @@ public class WebView extends AbsoluteLayout if (!wantsKeyEvents) return false; } - if (keyCode != KeyEvent.KEYCODE_SHIFT_LEFT - && keyCode != KeyEvent.KEYCODE_SHIFT_RIGHT) { - // turn off copy select if a shift-key combo is pressed - selectionDone(); - } - if (getSettings().getNavDump()) { switch (keyCode) { case KeyEvent.KEYCODE_4: @@ -4766,14 +4752,6 @@ public class WebView extends AbsoluteLayout return true; } - if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT - || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) { - if (!nativePageShouldHandleShiftAndArrows() && copySelection()) { - selectionDone(); - return true; - } - } - if (keyCode >= KeyEvent.KEYCODE_DPAD_UP && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) { if (nativePageShouldHandleShiftAndArrows()) { @@ -5234,7 +5212,8 @@ public class WebView extends AbsoluteLayout // Textfields, plugins, and contentEditable nodes need to receive the // shift up key even if another key was released while the shift key // was held down. - if (!inEditingMode() && (mNativeClass == 0 + boolean inEditingMode = inEditingMode(); + if (!inEditingMode && (mNativeClass == 0 || !nativePageShouldHandleShiftAndArrows())) { if (event.getAction() == KeyEvent.ACTION_DOWN) { mGotKeyDown = true; @@ -5251,7 +5230,13 @@ public class WebView extends AbsoluteLayout } if (dispatch) { - return super.dispatchKeyEvent(event); + if (inEditingMode) { + // Ensure that the WebTextView gets the event, even if it does + // not currently have a bounds. + return mWebTextView.dispatchKeyEvent(event); + } else { + return super.dispatchKeyEvent(event); + } } else { // We didn't dispatch, so let something else handle the key return false; diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index cc5be00d19e5..7675e0cb7c52 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -4780,7 +4780,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } hideControllers(); - stopSelectionActionMode(); switch (keyCode) { case KeyEvent.KEYCODE_DPAD_CENTER: @@ -5119,6 +5118,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (mInputMethodState != null) { mInputMethodState.mExtracting = req; } + // This stops a possible text selection mode. Maybe not intended. hideControllers(); } @@ -6781,7 +6781,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener sendOnTextChanged(buffer, start, before, after); onTextChanged(buffer, start, before, after); - // Hide the controller if the amount of content changed + // Hide the controllers if the amount of content changed if (before != after) { hideControllers(); } @@ -8207,9 +8207,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener selectCurrentWord(); } - final InputMethodManager imm = (InputMethodManager) - getContext().getSystemService(Context.INPUT_METHOD_SERVICE); - imm.showSoftInput(this, 0, null); + if (!mTextIsSelectable) { + // Show the IME, except when selection non editable text. + final InputMethodManager imm = (InputMethodManager) + getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + imm.showSoftInput(this, 0, null); + } ActionMode.Callback actionModeCallback = new SelectionActionModeCallback(); mSelectionActionMode = startActionMode(actionModeCallback); @@ -8233,6 +8236,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private void stopSelectionActionMode() { if (mSelectionActionMode != null) { + // This will hide the mSelectionModifierCursorController mSelectionActionMode.finish(); } } @@ -8356,8 +8360,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (mCustomSelectionActionModeCallback != null) { mCustomSelectionActionModeCallback.onDestroyActionMode(mode); } - Selection.setSelection((Spannable) mText, getSelectionStart()); - hideSelectionModifierCursorController(); + Selection.setSelection((Spannable) mText, getSelectionEnd()); + + if (mSelectionModifierCursorController != null) { + mSelectionModifierCursorController.hide(); + } + mSelectionActionMode = null; } } @@ -9173,16 +9181,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } - private void hideSelectionModifierCursorController() { - // No need to create the controller to hide it. - if (mSelectionModifierCursorController != null) { - mSelectionModifierCursorController.hide(); - } - } - + /** + * Hides the insertion controller and stops text selection mode, hiding the selection controller + */ private void hideControllers() { hideInsertionPointCursorController(); - hideSelectionModifierCursorController(); + stopSelectionActionMode(); } /** diff --git a/core/jni/com_android_internal_os_ZygoteInit.cpp b/core/jni/com_android_internal_os_ZygoteInit.cpp index ada4dd339be2..52e8f4251bb9 100644 --- a/core/jni/com_android_internal_os_ZygoteInit.cpp +++ b/core/jni/com_android_internal_os_ZygoteInit.cpp @@ -45,14 +45,10 @@ namespace android { static jint com_android_internal_os_ZygoteInit_setreuid( JNIEnv* env, jobject clazz, jint ruid, jint euid) { - int err; - - errno = 0; - err = setreuid(ruid, euid); - - //LOGI("setreuid(%d,%d) err %d errno %d", ruid, euid, err, errno); - - return errno; + if (setreuid(ruid, euid) < 0) { + return errno; + } + return 0; } /* @@ -62,14 +58,10 @@ static jint com_android_internal_os_ZygoteInit_setreuid( static jint com_android_internal_os_ZygoteInit_setregid( JNIEnv* env, jobject clazz, jint rgid, jint egid) { - int err; - - errno = 0; - err = setregid(rgid, egid); - - //LOGI("setregid(%d,%d) err %d errno %d", rgid, egid, err, errno); - - return errno; + if (setregid(rgid, egid) < 0) { + return errno; + } + return 0; } /* @@ -79,13 +71,10 @@ static jint com_android_internal_os_ZygoteInit_setregid( static jint com_android_internal_os_ZygoteInit_setpgid( JNIEnv* env, jobject clazz, jint pid, jint pgid) { - int err; - - errno = 0; - - err = setpgid(pid, pgid); - - return errno; + if (setpgid(pid, pgid) < 0) { + return errno; + } + return 0; } /* diff --git a/core/tests/coretests/src/android/util/JsonReaderTest.java b/core/tests/coretests/src/android/util/JsonReaderTest.java index 216a772dc412..b5c2c2743f33 100644 --- a/core/tests/coretests/src/android/util/JsonReaderTest.java +++ b/core/tests/coretests/src/android/util/JsonReaderTest.java @@ -16,11 +16,10 @@ package android.util; -import java.util.Arrays; -import junit.framework.TestCase; - import java.io.IOException; import java.io.StringReader; +import java.util.Arrays; +import junit.framework.TestCase; public final class JsonReaderTest extends TestCase { @@ -677,7 +676,7 @@ public final class JsonReaderTest extends TestCase { try { reader.nextString(); fail(); - } catch (IOException expected) { + } catch (MalformedJsonException expected) { } } @@ -811,4 +810,50 @@ public final class JsonReaderTest extends TestCase { reader.nextNull(); reader.endArray(); } + + public void testStrictMultipleTopLevelValues() throws IOException { + JsonReader reader = new JsonReader(new StringReader("[] []")); + reader.beginArray(); + reader.endArray(); + try { + reader.peek(); + fail(); + } catch (IOException expected) { + } + } + + public void testLenientMultipleTopLevelValues() throws IOException { + JsonReader reader = new JsonReader(new StringReader("[] true {}")); + reader.setLenient(true); + reader.beginArray(); + reader.endArray(); + assertEquals(true, reader.nextBoolean()); + reader.beginObject(); + reader.endObject(); + assertEquals(JsonToken.END_DOCUMENT, reader.peek()); + } + + public void testStrictTopLevelValueType() { + JsonReader reader = new JsonReader(new StringReader("true")); + try { + reader.nextBoolean(); + fail(); + } catch (IOException expected) { + } + } + + public void testLenientTopLevelValueType() throws IOException { + JsonReader reader = new JsonReader(new StringReader("true")); + reader.setLenient(true); + assertEquals(true, reader.nextBoolean()); + } + + public void testStrictNonExecutePrefix() { + JsonReader reader = new JsonReader(new StringReader(")]}'\n []")); + try { + reader.beginArray(); + fail(); + } catch (IOException expected) { + } + } } diff --git a/core/tests/coretests/src/android/util/JsonWriterTest.java b/core/tests/coretests/src/android/util/JsonWriterTest.java index fa840239d7b8..b29e2fdde08c 100644 --- a/core/tests/coretests/src/android/util/JsonWriterTest.java +++ b/core/tests/coretests/src/android/util/JsonWriterTest.java @@ -16,10 +16,11 @@ package android.util; -import junit.framework.TestCase; - import java.io.IOException; import java.io.StringWriter; +import java.math.BigDecimal; +import java.math.BigInteger; +import junit.framework.TestCase; public final class JsonWriterTest extends TestCase { @@ -119,7 +120,7 @@ public final class JsonWriterTest extends TestCase { JsonWriter jsonWriter = new JsonWriter(stringWriter); jsonWriter.beginObject(); jsonWriter.name("a"); - jsonWriter.value(null); + jsonWriter.value((String) null); jsonWriter.endObject(); assertEquals("{\"a\":null}", stringWriter.toString()); } @@ -145,6 +146,27 @@ public final class JsonWriterTest extends TestCase { } } + public void testNonFiniteBoxedDoubles() throws IOException { + StringWriter stringWriter = new StringWriter(); + JsonWriter jsonWriter = new JsonWriter(stringWriter); + jsonWriter.beginArray(); + try { + jsonWriter.value(new Double(Double.NaN)); + fail(); + } catch (IllegalArgumentException expected) { + } + try { + jsonWriter.value(new Double(Double.NEGATIVE_INFINITY)); + fail(); + } catch (IllegalArgumentException expected) { + } + try { + jsonWriter.value(new Double(Double.POSITIVE_INFINITY)); + fail(); + } catch (IllegalArgumentException expected) { + } + } + public void testDoubles() throws IOException { StringWriter stringWriter = new StringWriter(); JsonWriter jsonWriter = new JsonWriter(stringWriter); @@ -189,6 +211,22 @@ public final class JsonWriterTest extends TestCase { + "9223372036854775807]", stringWriter.toString()); } + public void testNumbers() throws IOException { + StringWriter stringWriter = new StringWriter(); + JsonWriter jsonWriter = new JsonWriter(stringWriter); + jsonWriter.beginArray(); + jsonWriter.value(new BigInteger("0")); + jsonWriter.value(new BigInteger("9223372036854775808")); + jsonWriter.value(new BigInteger("-9223372036854775809")); + jsonWriter.value(new BigDecimal("3.141592653589793238462643383")); + jsonWriter.endArray(); + jsonWriter.close(); + assertEquals("[0," + + "9223372036854775808," + + "-9223372036854775809," + + "3.141592653589793238462643383]", stringWriter.toString()); + } + public void testBooleans() throws IOException { StringWriter stringWriter = new StringWriter(); JsonWriter jsonWriter = new JsonWriter(stringWriter); diff --git a/include/gui/SurfaceTexture.h b/include/gui/SurfaceTexture.h index ff92e0858943..cbc15d81b980 100644 --- a/include/gui/SurfaceTexture.h +++ b/include/gui/SurfaceTexture.h @@ -113,8 +113,11 @@ private: int mBufferCount; // mCurrentTexture is the buffer slot index of the buffer that is currently - // bound to the OpenGL texture. A value of INVALID_BUFFER_SLOT, indicating - // that no buffer is currently bound to the texture. + // bound to the OpenGL texture. It is initialized to INVALID_BUFFER_SLOT, + // indicating that no buffer slot is currently bound to the texture. Note, + // however, that a value of INVALID_BUFFER_SLOT does not necessarily mean + // that no buffer is bound to the texture. A call to setBufferCount will + // reset mCurrentTexture to INVALID_BUFFER_SLOT. int mCurrentTexture; // mLastQueued is the buffer slot index of the most recently enqueued buffer. diff --git a/include/media/stagefright/ACodec.h b/include/media/stagefright/ACodec.h index b3815c4f24ac..4599d70d1faf 100644 --- a/include/media/stagefright/ACodec.h +++ b/include/media/stagefright/ACodec.h @@ -101,6 +101,8 @@ private: List<sp<AMessage> > mDeferredQueue; + bool mSentFormat; + status_t allocateBuffersOnPort(OMX_U32 portIndex); status_t freeBuffersOnPort(OMX_U32 portIndex); status_t freeBuffer(OMX_U32 portIndex, size_t i); @@ -145,6 +147,8 @@ private: void deferMessage(const sp<AMessage> &msg); void processDeferredMessages(); + void sendFormatChange(); + DISALLOW_EVIL_CONSTRUCTORS(ACodec); }; diff --git a/include/media/stagefright/foundation/AMessage.h b/include/media/stagefright/foundation/AMessage.h index 91ec60c894f3..72dc730191fd 100644 --- a/include/media/stagefright/foundation/AMessage.h +++ b/include/media/stagefright/foundation/AMessage.h @@ -52,6 +52,10 @@ struct AMessage : public RefBase { void setObject(const char *name, const sp<RefBase> &obj); void setMessage(const char *name, const sp<AMessage> &obj); + void setRect( + const char *name, + int32_t left, int32_t top, int32_t right, int32_t bottom); + bool findInt32(const char *name, int32_t *value) const; bool findInt64(const char *name, int64_t *value) const; bool findSize(const char *name, size_t *value) const; @@ -62,6 +66,10 @@ struct AMessage : public RefBase { bool findObject(const char *name, sp<RefBase> *obj) const; bool findMessage(const char *name, sp<AMessage> *obj) const; + bool findRect( + const char *name, + int32_t *left, int32_t *top, int32_t *right, int32_t *bottom) const; + void post(int64_t delayUs = 0); // Performs a deep-copy of "this", contained messages are in turn "dup'ed". @@ -85,11 +93,16 @@ private: kTypeString, kTypeObject, kTypeMessage, + kTypeRect, }; uint32_t mWhat; ALooper::handler_id mTarget; + struct Rect { + int32_t mLeft, mTop, mRight, mBottom; + }; + struct Item { union { int32_t int32Value; @@ -100,6 +113,7 @@ private: void *ptrValue; RefBase *refValue; AString *stringValue; + Rect rectValue; } u; const char *mName; Type mType; diff --git a/libs/gui/SurfaceTexture.cpp b/libs/gui/SurfaceTexture.cpp index 9579996342cc..11a48d989425 100644 --- a/libs/gui/SurfaceTexture.cpp +++ b/libs/gui/SurfaceTexture.cpp @@ -15,6 +15,7 @@ */ #define LOG_TAG "SurfaceTexture" +//#define LOG_NDEBUG 0 #define GL_GLEXT_PROTOTYPES #define EGL_EGLEXT_PROTOTYPES @@ -36,21 +37,32 @@ namespace android { SurfaceTexture::SurfaceTexture(GLuint tex) : mBufferCount(MIN_BUFFER_SLOTS), mCurrentTexture(INVALID_BUFFER_SLOT), mLastQueued(INVALID_BUFFER_SLOT), mTexName(tex) { + LOGV("SurfaceTexture::SurfaceTexture"); + for (int i = 0; i < NUM_BUFFER_SLOTS; i++) { + mSlots[i].mEglImage = EGL_NO_IMAGE_KHR; + mSlots[i].mEglDisplay = EGL_NO_DISPLAY; + mSlots[i].mOwnedByClient = false; + } } SurfaceTexture::~SurfaceTexture() { + LOGV("SurfaceTexture::~SurfaceTexture"); freeAllBuffers(); } status_t SurfaceTexture::setBufferCount(int bufferCount) { + LOGV("SurfaceTexture::setBufferCount"); Mutex::Autolock lock(mMutex); freeAllBuffers(); mBufferCount = bufferCount; + mCurrentTexture = INVALID_BUFFER_SLOT; + mLastQueued = INVALID_BUFFER_SLOT; return OK; } sp<GraphicBuffer> SurfaceTexture::requestBuffer(int buf, uint32_t w, uint32_t h, uint32_t format, uint32_t usage) { + LOGV("SurfaceTexture::requestBuffer"); Mutex::Autolock lock(mMutex); if (buf < 0 || mBufferCount <= buf) { LOGE("requestBuffer: slot index out of range [0, %d]: %d", @@ -75,6 +87,7 @@ sp<GraphicBuffer> SurfaceTexture::requestBuffer(int buf, } status_t SurfaceTexture::dequeueBuffer(int *buf) { + LOGV("SurfaceTexture::dequeueBuffer"); Mutex::Autolock lock(mMutex); int found = INVALID_BUFFER_SLOT; for (int i = 0; i < mBufferCount; i++) { @@ -92,6 +105,7 @@ status_t SurfaceTexture::dequeueBuffer(int *buf) { } status_t SurfaceTexture::queueBuffer(int buf) { + LOGV("SurfaceTexture::queueBuffer"); Mutex::Autolock lock(mMutex); if (buf < 0 || mBufferCount <= buf) { LOGE("queueBuffer: slot index out of range [0, %d]: %d", @@ -111,6 +125,7 @@ status_t SurfaceTexture::queueBuffer(int buf) { } void SurfaceTexture::cancelBuffer(int buf) { + LOGV("SurfaceTexture::cancelBuffer"); Mutex::Autolock lock(mMutex); if (buf < 0 || mBufferCount <= buf) { LOGE("cancelBuffer: slot index out of range [0, %d]: %d", mBufferCount, @@ -124,18 +139,21 @@ void SurfaceTexture::cancelBuffer(int buf) { } status_t SurfaceTexture::setCrop(const Rect& reg) { + LOGV("SurfaceTexture::setCrop"); Mutex::Autolock lock(mMutex); // XXX: How should we handle crops? return OK; } status_t SurfaceTexture::setTransform(uint32_t transform) { + LOGV("SurfaceTexture::setTransform"); Mutex::Autolock lock(mMutex); // XXX: How should we handle transforms? return OK; } status_t SurfaceTexture::updateTexImage() { + LOGV("SurfaceTexture::updateTexImage"); Mutex::Autolock lock(mMutex); // We always bind the texture even if we don't update its contents. diff --git a/media/libmediaplayerservice/nuplayer/DecoderWrapper.cpp b/media/libmediaplayerservice/nuplayer/DecoderWrapper.cpp index 4d4cd8dd807c..802d1fb1b263 100644 --- a/media/libmediaplayerservice/nuplayer/DecoderWrapper.cpp +++ b/media/libmediaplayerservice/nuplayer/DecoderWrapper.cpp @@ -309,6 +309,18 @@ void DecoderWrapper::WrapperReader::sendFormatChange() { realNotify->setInt32("width", width); realNotify->setInt32("height", height); + + int32_t cropLeft, cropTop, cropRight, cropBottom; + if (!meta->findRect( + kKeyCropRect, + &cropLeft, &cropTop, &cropRight, &cropBottom)) { + cropLeft = 0; + cropTop = 0; + cropRight = width - 1; + cropBottom = height - 1; + } + + realNotify->setRect("crop", cropLeft, cropTop, cropRight, cropBottom); } notify->post(); diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp index e1b371eb7c73..7f534c07115b 100644 --- a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp +++ b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp @@ -271,22 +271,43 @@ void NuPlayer::onMessageReceived(const sp<AMessage> &msg) { finishFlushIfPossible(); } else if (what == ACodec::kWhatOutputFormatChanged) { - CHECK(audio); - - int32_t numChannels; - CHECK(codecRequest->findInt32("channel-count", &numChannels)); + if (audio) { + int32_t numChannels; + CHECK(codecRequest->findInt32("channel-count", &numChannels)); - int32_t sampleRate; - CHECK(codecRequest->findInt32("sample-rate", &sampleRate)); + int32_t sampleRate; + CHECK(codecRequest->findInt32("sample-rate", &sampleRate)); - LOGV("Audio output format changed to %d Hz, %d channels", - sampleRate, numChannels); + LOGV("Audio output format changed to %d Hz, %d channels", + sampleRate, numChannels); - mAudioSink->close(); - CHECK_EQ(mAudioSink->open(sampleRate, numChannels), (status_t)OK); - mAudioSink->start(); + mAudioSink->close(); + CHECK_EQ(mAudioSink->open(sampleRate, numChannels), (status_t)OK); + mAudioSink->start(); - mRenderer->signalAudioSinkChanged(); + mRenderer->signalAudioSinkChanged(); + } else { + // video + + int32_t width, height; + CHECK(codecRequest->findInt32("width", &width)); + CHECK(codecRequest->findInt32("height", &height)); + + int32_t cropLeft, cropTop, cropRight, cropBottom; + CHECK(codecRequest->findRect( + "crop", + &cropLeft, &cropTop, &cropRight, &cropBottom)); + + LOGV("Video output format changed to %d x %d " + "(crop: %d, %d, %d, %d)", + width, height, + cropLeft, cropTop, cropRight, cropBottom); + + notifyListener( + MEDIA_SET_VIDEO_SIZE, + cropRight - cropLeft + 1, + cropBottom - cropTop + 1); + } } else if (what == ACodec::kWhatShutdownCompleted) { LOGV("%s shutdown completed", audio ? "audio" : "video"); if (audio) { diff --git a/media/libstagefright/ACodec.cpp b/media/libstagefright/ACodec.cpp index 39e0c5105b44..dfb4e000e6bf 100644 --- a/media/libstagefright/ACodec.cpp +++ b/media/libstagefright/ACodec.cpp @@ -299,7 +299,8 @@ private: //////////////////////////////////////////////////////////////////////////////// ACodec::ACodec() - : mNode(NULL) { + : mNode(NULL), + mSentFormat(false) { mUninitializedState = new UninitializedState(this); mLoadedToIdleState = new LoadedToIdleState(this); mIdleToExecutingState = new IdleToExecutingState(this); @@ -980,6 +981,103 @@ void ACodec::processDeferredMessages() { } } +void ACodec::sendFormatChange() { + sp<AMessage> notify = mNotify->dup(); + notify->setInt32("what", kWhatOutputFormatChanged); + + OMX_PARAM_PORTDEFINITIONTYPE def; + InitOMXParams(&def); + def.nPortIndex = kPortIndexOutput; + + CHECK_EQ(mOMX->getParameter( + mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)), + (status_t)OK); + + CHECK_EQ((int)def.eDir, (int)OMX_DirOutput); + + switch (def.eDomain) { + case OMX_PortDomainVideo: + { + OMX_VIDEO_PORTDEFINITIONTYPE *videoDef = &def.format.video; + + notify->setString("mime", MEDIA_MIMETYPE_VIDEO_RAW); + notify->setInt32("width", videoDef->nFrameWidth); + notify->setInt32("height", videoDef->nFrameHeight); + + OMX_CONFIG_RECTTYPE rect; + InitOMXParams(&rect); + rect.nPortIndex = kPortIndexOutput; + + if (mOMX->getConfig( + mNode, OMX_IndexConfigCommonOutputCrop, + &rect, sizeof(rect)) != OK) { + rect.nLeft = 0; + rect.nTop = 0; + rect.nWidth = videoDef->nFrameWidth; + rect.nHeight = videoDef->nFrameHeight; + } + + CHECK_GE(rect.nLeft, 0); + CHECK_GE(rect.nTop, 0); + CHECK_GE(rect.nWidth, 0u); + CHECK_GE(rect.nHeight, 0u); + CHECK_LE(rect.nLeft + rect.nWidth - 1, videoDef->nFrameWidth); + CHECK_LE(rect.nTop + rect.nHeight - 1, videoDef->nFrameHeight); + + notify->setRect( + "crop", + rect.nLeft, + rect.nTop, + rect.nLeft + rect.nWidth - 1, + rect.nTop + rect.nHeight - 1); + + if (mNativeWindow != NULL) { + android_native_rect_t crop; + crop.left = rect.nLeft; + crop.top = rect.nTop; + crop.right = rect.nLeft + rect.nWidth - 1; + crop.bottom = rect.nTop + rect.nHeight - 1; + + CHECK_EQ(0, native_window_set_crop( + mNativeWindow.get(), &crop)); + } + break; + } + + case OMX_PortDomainAudio: + { + OMX_AUDIO_PORTDEFINITIONTYPE *audioDef = &def.format.audio; + CHECK_EQ((int)audioDef->eEncoding, (int)OMX_AUDIO_CodingPCM); + + OMX_AUDIO_PARAM_PCMMODETYPE params; + InitOMXParams(¶ms); + params.nPortIndex = kPortIndexOutput; + + CHECK_EQ(mOMX->getParameter( + mNode, OMX_IndexParamAudioPcm, + ¶ms, sizeof(params)), + (status_t)OK); + + CHECK(params.nChannels == 1 || params.bInterleaved); + CHECK_EQ(params.nBitPerSample, 16u); + CHECK_EQ((int)params.eNumData, (int)OMX_NumericalDataSigned); + CHECK_EQ((int)params.ePCMMode, (int)OMX_AUDIO_PCMModeLinear); + + notify->setString("mime", MEDIA_MIMETYPE_AUDIO_RAW); + notify->setInt32("channel-count", params.nChannels); + notify->setInt32("sample-rate", params.nSamplingRate); + break; + } + + default: + TRESPASS(); + } + + notify->post(); + + mSentFormat = true; +} + //////////////////////////////////////////////////////////////////////////////// ACodec::BaseState::BaseState(ACodec *codec, const sp<AState> &parentState) @@ -1305,6 +1403,10 @@ bool ACodec::BaseState::onOMXFillBufferDone( info->mStatus = BufferInfo::OWNED_BY_COMPONENT; } } else { + if (!mCodec->mSentFormat) { + mCodec->sendFormatChange(); + } + if (mCodec->mNativeWindow == NULL) { info->mData->setRange(rangeOffset, rangeLength); } @@ -1717,7 +1819,7 @@ bool ACodec::ExecutingState::onOMXEvent( { CHECK_EQ(data1, (OMX_U32)kPortIndexOutput); - if (data2 == OMX_IndexParamPortDefinition) { + if (data2 == 0 || data2 == OMX_IndexParamPortDefinition) { CHECK_EQ(mCodec->mOMX->sendCommand( mCodec->mNode, OMX_CommandPortDisable, kPortIndexOutput), @@ -1729,6 +1831,8 @@ bool ACodec::ExecutingState::onOMXEvent( } mCodec->changeState(mCodec->mOutputPortSettingsChangedState); + } else if (data2 == OMX_IndexConfigCommonOutputCrop) { + mCodec->mSentFormat = false; } else { LOGV("[%s] OMX_EventPortSettingsChanged 0x%08lx", mCodec->mComponentName.c_str(), data2); @@ -1816,6 +1920,8 @@ bool ACodec::OutputPortSettingsChangedState::onOMXEvent( } else if (data1 == (OMX_U32)OMX_CommandPortEnable) { CHECK_EQ(data2, (OMX_U32)kPortIndexOutput); + mCodec->mSentFormat = false; + LOGV("[%s] Output port now reenabled.", mCodec->mComponentName.c_str()); @@ -1869,6 +1975,8 @@ bool ACodec::ExecutingToIdleState::onMessageReceived(const sp<AMessage> &msg) { void ACodec::ExecutingToIdleState::stateEntered() { LOGV("[%s] Now Executing->Idle", mCodec->mComponentName.c_str()); + + mCodec->mSentFormat = false; } bool ACodec::ExecutingToIdleState::onOMXEvent( diff --git a/media/libstagefright/codecs/m4v_h263/dec/M4vH263Decoder.cpp b/media/libstagefright/codecs/m4v_h263/dec/M4vH263Decoder.cpp index 0f08f6e9fd18..38778fb3aa71 100644 --- a/media/libstagefright/codecs/m4v_h263/dec/M4vH263Decoder.cpp +++ b/media/libstagefright/codecs/m4v_h263/dec/M4vH263Decoder.cpp @@ -132,7 +132,10 @@ status_t M4vH263Decoder::start(MetaData *) { } MP4DecodingMode actualMode = PVGetDecBitstreamMode(mHandle); - CHECK_EQ(mode, actualMode); + if (mode != actualMode) { + PVCleanUpVideoDecoder(mHandle); + return UNKNOWN_ERROR; + } PVSetPostProcType((VideoDecControls *) mHandle, 0); diff --git a/media/libstagefright/foundation/AMessage.cpp b/media/libstagefright/foundation/AMessage.cpp index 0e40acc505d2..b592c3fc3e06 100644 --- a/media/libstagefright/foundation/AMessage.cpp +++ b/media/libstagefright/foundation/AMessage.cpp @@ -171,6 +171,18 @@ void AMessage::setMessage(const char *name, const sp<AMessage> &obj) { item->u.refValue = obj.get(); } +void AMessage::setRect( + const char *name, + int32_t left, int32_t top, int32_t right, int32_t bottom) { + Item *item = allocateItem(name); + item->mType = kTypeRect; + + item->u.rectValue.mLeft = left; + item->u.rectValue.mTop = top; + item->u.rectValue.mRight = right; + item->u.rectValue.mBottom = bottom; +} + bool AMessage::findString(const char *name, AString *value) const { const Item *item = findItem(name, kTypeString); if (item) { @@ -198,6 +210,22 @@ bool AMessage::findMessage(const char *name, sp<AMessage> *obj) const { return false; } +bool AMessage::findRect( + const char *name, + int32_t *left, int32_t *top, int32_t *right, int32_t *bottom) const { + const Item *item = findItem(name, kTypeRect); + if (item == NULL) { + return false; + } + + *left = item->u.rectValue.mLeft; + *top = item->u.rectValue.mTop; + *right = item->u.rectValue.mRight; + *bottom = item->u.rectValue.mBottom; + + return true; +} + void AMessage::post(int64_t delayUs) { extern ALooperRoster gLooperRoster; diff --git a/tests/CoreTests/android/core/HeapTest.java b/tests/CoreTests/android/core/HeapTest.java index 6116f5e0b5e6..98cb28ae26fe 100644 --- a/tests/CoreTests/android/core/HeapTest.java +++ b/tests/CoreTests/android/core/HeapTest.java @@ -540,94 +540,4 @@ public class HeapTest extends TestCase { objSize = (objSize * 4) / 5; } } - - // TODO: flaky test - //@SmallTest - public void testExternalOomeLarge() { - /* Just shy of the typical max heap size so that it will actually - * try to allocate it instead of short-circuiting. - */ - final int HUGE_SIZE = (16 * 1024 * 1024 - 32); - - assertFalse(VMRuntime.getRuntime().trackExternalAllocation(HUGE_SIZE)); - } - - /** - * "Allocates" external memory in progressively smaller chunks until there's - * only roughly 16 bytes left. - * - * @return the number of bytes allocated - */ - private long allocateMaxExternal() { - final VMRuntime runtime = VMRuntime.getRuntime(); - final int SIXTEEN_MB = (16 * 1024 * 1024); - final int MIN_SIZE = 16; - long totalAllocated = 0; - boolean success; - - success = false; - try { - /* "Allocate" progressively smaller chunks to "fill up" the entire heap. - */ - int objSize = 1 * 1024 * 1024; - while (objSize >= MIN_SIZE) { - boolean sawFailure = false; - for (int i = 0; i < SIXTEEN_MB / objSize; i++) { - if (runtime.trackExternalAllocation(objSize)) { - totalAllocated += objSize; - } else { - sawFailure = true; - break; - } - } - - if (!sawFailure) { - throw new RuntimeException("Test failed: " + - "no failure while filling heap"); - } - - objSize = (objSize * 4) / 5; - } - success = true; - } finally { - if (!success) { - runtime.trackExternalFree(totalAllocated); - totalAllocated = 0; - } - } - return totalAllocated; - } - - public void xxtest00ExternalOomeSmall() { - VMRuntime.getRuntime().trackExternalFree(allocateMaxExternal()); - } - - /** - * Allocates as much external memory as possible, then allocates from the heap - * until an OOME is caught. - * - * It's nice to run this test while the real heap is small, hence the '00' in its - * name to force it to run before testOomeSmall(). - */ - public void xxtest00CombinedOomeSmall() { - long totalAllocated = 0; - boolean sawEx = false; - try { - totalAllocated = allocateMaxExternal(); - LinkedList<Object> list = new LinkedList<Object>(); - try { - while (true) { - list.add((Object)new byte[8192]); - } - /*NOTREACHED*/ - } catch (OutOfMemoryError oom) { - sawEx = true; - } - } finally { - VMRuntime.getRuntime().trackExternalFree(totalAllocated); - } - assertTrue(sawEx); - } - - //TODO: test external alloc debugging/inspection } 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 e1bf9251e0b1..c042327ebf1b 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java @@ -195,7 +195,8 @@ public final class Bridge extends com.android.ide.common.rendering.api.Bridge { Capability.RENDER, Capability.EMBEDDED_LAYOUT, Capability.VIEW_MANIPULATION, - Capability.ANIMATE); + Capability.PLAY_ANIMATION, + Capability.ANIMATED_VIEW_MANIPULATION); BridgeAssetManager.initSystem(); diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java index 72692274b0c2..b0eea4cacb49 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java @@ -92,7 +92,7 @@ public final class BridgeContext extends Activity { // cache for TypedArray generated from IStyleResourceValue object private Map<int[], Map<Integer, TypedArray>> mTypedArrayCache; - private BridgeInflater mInflater; + private BridgeInflater mBridgeInflater; private final IProjectCallback mProjectCallback; private BridgeContentResolver mContentResolver; @@ -160,7 +160,7 @@ public final class BridgeContext extends Activity { } public void setBridgeInflater(BridgeInflater inflater) { - mInflater = inflater; + mBridgeInflater = inflater; } public void addViewKey(View view, Object viewKey) { @@ -221,7 +221,7 @@ public final class BridgeContext extends Activity { @Override public LayoutInflater getLayoutInflater() { - return mInflater; + return mBridgeInflater; } // ------------ Context methods @@ -244,7 +244,7 @@ public final class BridgeContext extends Activity { @Override public Object getSystemService(String service) { if (LAYOUT_INFLATER_SERVICE.equals(service)) { - return mInflater; + return mBridgeInflater; } // AutoCompleteTextView and MultiAutoCompleteTextView want a window @@ -591,7 +591,7 @@ public final class BridgeContext extends Activity { boolean frameworkOnly = false; - // eleminate the prefix from the string + // eliminate the prefix from the string if (reference.startsWith(BridgeConstants.PREFIX_ANDROID_THEME_REF)) { frameworkOnly = true; reference = reference.substring(BridgeConstants.PREFIX_ANDROID_THEME_REF.length()); @@ -677,7 +677,7 @@ public final class BridgeContext extends Activity { * project resources */ private ResourceValue findResValue(String resType, String resName, boolean frameworkOnly) { - // map of IResouceValue for the given type + // map of ResouceValue for the given type Map<String, ResourceValue> typeMap; // if allowed, search in the project resources first. @@ -701,6 +701,13 @@ public final class BridgeContext extends Activity { } // didn't find the resource anywhere. + // This is normal if the resource is an ID that is generated automatically. + // For other resources, we output a warning + if ("+id".equals(resType) == false && "+android:id".equals(resType) == false) { //$NON-NLS-1$ //$NON-NLS-2$ + Bridge.getLog().warning("resources", //$NON-NLS-1$ + "Couldn't resolve resource @" + + (frameworkOnly ? "android:" : "") + resType + "/" + resName); + } return null; } @@ -1303,4 +1310,15 @@ public final class BridgeContext extends Activity { public Context getApplicationContext() { throw new UnsupportedOperationException(); } + + @Override + public void startActivities(Intent[] arg0) { + // TODO Auto-generated method stub + + } + + @Override + public boolean isRestricted() { + return false; + } } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeTypedArray.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeTypedArray.java index 6ae7e032c259..886ef2ac5729 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeTypedArray.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeTypedArray.java @@ -43,25 +43,25 @@ import java.util.Map; */ public final class BridgeTypedArray extends TypedArray { - private BridgeResources mResources; + private BridgeResources mBridgeResources; private BridgeContext mContext; - private ResourceValue[] mData; + private ResourceValue[] mResourceData; private String[] mNames; private final boolean mPlatformFile; public BridgeTypedArray(BridgeResources resources, BridgeContext context, int len, boolean platformFile) { super(null, null, null, 0); - mResources = resources; + mBridgeResources = resources; mContext = context; mPlatformFile = platformFile; - mData = new ResourceValue[len]; + mResourceData = new ResourceValue[len]; mNames = new String[len]; } /** A bridge-specific method that sets a value in the type array */ public void bridgeSetValue(int index, String name, ResourceValue value) { - mData[index] = value; + mResourceData[index] = value; mNames[index] = name; } @@ -75,7 +75,7 @@ public final class BridgeTypedArray extends TypedArray { // fills TypedArray.mIndices which is used to implement getIndexCount/getIndexAt // first count the array size int count = 0; - for (ResourceValue data : mData) { + for (ResourceValue data : mResourceData) { if (data != null) { count++; } @@ -87,8 +87,8 @@ public final class BridgeTypedArray extends TypedArray { // fill the array with the indices. int index = 1; - for (int i = 0 ; i < mData.length ; i++) { - if (mData[i] != null) { + for (int i = 0 ; i < mResourceData.length ; i++) { + if (mResourceData[i] != null) { mIndices[index++] = i; } } @@ -99,7 +99,7 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public int length() { - return mData.length; + return mResourceData.length; } /** @@ -107,7 +107,7 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public Resources getResources() { - return mResources; + return mBridgeResources; } /** @@ -120,9 +120,9 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public CharSequence getText(int index) { - if (mData[index] != null) { + if (mResourceData[index] != null) { // FIXME: handle styled strings! - return mData[index].getValue(); + return mResourceData[index].getValue(); } return null; @@ -138,8 +138,8 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public String getString(int index) { - if (mData[index] != null) { - return mData[index].getValue(); + if (mResourceData[index] != null) { + return mResourceData[index].getValue(); } return null; @@ -155,11 +155,11 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public boolean getBoolean(int index, boolean defValue) { - if (mData[index] == null) { + if (mResourceData[index] == null) { return defValue; } - String s = mData[index].getValue(); + String s = mResourceData[index].getValue(); if (s != null) { return XmlUtils.convertValueToBoolean(s, defValue); } @@ -177,11 +177,11 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public int getInt(int index, int defValue) { - if (mData[index] == null) { + if (mResourceData[index] == null) { return defValue; } - String s = mData[index].getValue(); + String s = mResourceData[index].getValue(); try { return (s == null) ? defValue : XmlUtils.convertValueToInt(s, defValue); @@ -225,11 +225,11 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public float getFloat(int index, float defValue) { - if (mData[index] == null) { + if (mResourceData[index] == null) { return defValue; } - String s = mData[index].getValue(); + String s = mResourceData[index].getValue(); if (s != null) { try { @@ -259,11 +259,11 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public int getColor(int index, int defValue) { - if (mData[index] == null) { + if (mResourceData[index] == null) { return defValue; } - String s = mData[index].getValue(); + String s = mResourceData[index].getValue(); try { return ResourceHelper.getColor(s); } catch (NumberFormatException e) { @@ -288,11 +288,11 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public ColorStateList getColorStateList(int index) { - if (mData[index] == null) { + if (mResourceData[index] == null) { return null; } - String value = mData[index].getValue(); + String value = mResourceData[index].getValue(); if (value == null) { return null; @@ -347,11 +347,11 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public int getInteger(int index, int defValue) { - if (mData[index] == null) { + if (mResourceData[index] == null) { return defValue; } - String s = mData[index].getValue(); + String s = mResourceData[index].getValue(); if (s != null) { try { @@ -386,11 +386,11 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public float getDimension(int index, float defValue) { - if (mData[index] == null) { + if (mResourceData[index] == null) { return defValue; } - String s = mData[index].getValue(); + String s = mResourceData[index].getValue(); if (s == null) { return defValue; @@ -402,7 +402,7 @@ public final class BridgeTypedArray extends TypedArray { } if (ResourceHelper.stringToFloat(s, mValue)) { - return mValue.getDimension(mResources.mMetrics); + return mValue.getDimension(mBridgeResources.mMetrics); } // looks like we were unable to resolve the dimension value @@ -456,11 +456,11 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public int getDimensionPixelSize(int index, int defValue) { - if (mData[index] == null) { + if (mResourceData[index] == null) { return defValue; } - String s = mData[index].getValue(); + String s = mResourceData[index].getValue(); if (s == null) { return defValue; @@ -522,11 +522,11 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public float getFraction(int index, int base, int pbase, float defValue) { - if (mData[index] == null) { + if (mResourceData[index] == null) { return defValue; } - String value = mData[index].getValue(); + String value = mResourceData[index].getValue(); if (value == null) { return defValue; } @@ -560,8 +560,8 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public int getResourceId(int index, int defValue) { - // get the IResource for this index - ResourceValue resValue = mData[index]; + // get the Resource for this index + ResourceValue resValue = mResourceData[index]; // no data, return the default value. if (resValue == null) { @@ -575,7 +575,7 @@ public final class BridgeTypedArray extends TypedArray { } // if the attribute was a reference to an id, and not a declaration of an id (@+id), then - // the xml attribute value was "resolved" which leads us to a IResourceValue with + // the xml attribute value was "resolved" which leads us to a ResourceValue with // getType() returning "id" and getName() returning the id name // (and getValue() returning null!). We need to handle this! if (resValue.getType() != null && resValue.getType().equals(BridgeConstants.RES_ID)) { @@ -662,17 +662,17 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public Drawable getDrawable(int index) { - if (mData[index] == null) { + if (mResourceData[index] == null) { return null; } - ResourceValue value = mData[index]; + ResourceValue value = mResourceData[index]; String stringValue = value.getValue(); if (stringValue == null || BridgeConstants.REFERENCE_NULL.equals(stringValue)) { return null; } - Drawable d = ResourceHelper.getDrawable(value, mContext, mData[index].isFramework()); + Drawable d = ResourceHelper.getDrawable(value, mContext, mResourceData[index].isFramework()); if (d != null) { return d; @@ -700,18 +700,18 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public CharSequence[] getTextArray(int index) { - if (mData[index] == null) { + if (mResourceData[index] == null) { return null; } - String value = mData[index].getValue(); + String value = mResourceData[index].getValue(); if (value != null) { return new CharSequence[] { value }; } Bridge.getLog().warning(null, String.format( String.format("Unknown value for getTextArray(%d) => %s", //DEBUG - index, mData[index].getName()))); + index, mResourceData[index].getName()))); return null; } @@ -727,11 +727,11 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public boolean getValue(int index, TypedValue outValue) { - if (mData[index] == null) { + if (mResourceData[index] == null) { return false; } - String s = mData[index].getValue(); + String s = mResourceData[index].getValue(); return ResourceHelper.stringToFloat(s, outValue); } @@ -745,7 +745,7 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public boolean hasValue(int index) { - return mData[index] != null; + return mResourceData[index] != null; } /** @@ -792,6 +792,6 @@ public final class BridgeTypedArray extends TypedArray { @Override public String toString() { - return mData.toString(); + return mResourceData.toString(); } } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/AnimationThread.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/AnimationThread.java index 125c1e6b6b70..033e065cb479 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/AnimationThread.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/AnimationThread.java @@ -28,7 +28,7 @@ import android.os.Handler_Delegate; import android.os.Message; import android.os.Handler_Delegate.IHandlerCallback; -import java.util.LinkedList; +import java.util.PriorityQueue; import java.util.Queue; /** @@ -45,7 +45,7 @@ import java.util.Queue; */ public abstract class AnimationThread extends Thread { - private static class MessageBundle { + private static class MessageBundle implements Comparable<MessageBundle> { final Handler mTarget; final Message mMessage; final long mUptimeMillis; @@ -55,11 +55,18 @@ public abstract class AnimationThread extends Thread { mMessage = message; mUptimeMillis = uptimeMillis; } + + public int compareTo(MessageBundle bundle) { + if (mUptimeMillis < bundle.mUptimeMillis) { + return -1; + } + return 1; + } } private final RenderSessionImpl mSession; - private Queue<MessageBundle> mQueue = new LinkedList<MessageBundle>(); + private Queue<MessageBundle> mQueue = new PriorityQueue<MessageBundle>(); private final IAnimationListener mListener; public AnimationThread(RenderSessionImpl scene, String threadName, diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java index 025a318f0e57..55a5bc084899 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java @@ -630,7 +630,7 @@ public class RenderSessionImpl { * @see LayoutScene#moveChild(Object, Object, int, Map, IAnimationListener) */ public Result moveChild(final ViewGroup newParentView, final View childView, final int index, - Map<String, String> layoutParamsMap, IAnimationListener listener) { + Map<String, String> layoutParamsMap, final IAnimationListener listener) { checkLock(); invalidateRenderingSize(); @@ -647,42 +647,73 @@ public class RenderSessionImpl { if (listener != null) { final LayoutParams params = layoutParams; - new AnimationThread(this, "moveChild", listener) { - @Override - public Result preAnimation() { - // set up the transition for the previous parent. - LayoutTransition removeTransition = new LayoutTransition(); - previousParent.setLayoutTransition(removeTransition); - - // no fade-out. Because we can't rely on layout transition listeners when - // there is no Animator at all, instead we keep the animator but set its - // duration to 0. - // Note: Cannot user Animation.setDuration() directly. Have to set it - // on the LayoutTransition. - removeTransition.setDuration(LayoutTransition.DISAPPEARING, 0); - - if (previousParent != newParentView) { - // different parent, set a Layout transition on the new parent. - newParentView.setLayoutTransition(new LayoutTransition()); + // there is no support for animating views across layouts, so in case the new and old + // parent views are different we fake the animation through a no animation thread. + if (previousParent != newParentView) { + new Thread("not animated moveChild") { + @Override + public void run() { + Result result = moveView(previousParent, newParentView, childView, index, + params); + if (result.isSuccess() == false) { + listener.done(result); + } + + // ready to do the work, acquire the scene. + result = acquire(250); + if (result.isSuccess() == false) { + listener.done(result); + return; + } + + try { + result = render(); + if (result.isSuccess()) { + listener.onNewFrame(RenderSessionImpl.this.getSession()); + } + } finally { + release(); + } + + listener.done(result); } + }.start(); + } else { + new AnimationThread(this, "moveChild", listener) { - // no fade-in. Because we can't rely on layout transition listeners when - // there is no Animator at all, instead we keep the animator but set its - // duration to 0. - // Note: Cannot user Animation.setDuration() directly. Have to set it - // on the LayoutTransition. - newParentView.getLayoutTransition().setDuration(LayoutTransition.APPEARING, 0); + @Override + public Result preAnimation() { + // set up the transition for the parent. + LayoutTransition transition = new LayoutTransition(); + previousParent.setLayoutTransition(transition); - return moveView(previousParent, newParentView, childView, index, params); - } + // tweak the animation durations and start delays (to match the duration of + // animation playing just before). + // Note: Cannot user Animation.setDuration() directly. Have to set it + // on the LayoutTransition. + transition.setDuration(LayoutTransition.DISAPPEARING, 100); + // CHANGE_DISAPPEARING plays after DISAPPEARING + transition.setStartDelay(LayoutTransition.CHANGE_DISAPPEARING, 100); - @Override - public void postAnimation() { - previousParent.setLayoutTransition(null); - newParentView.setLayoutTransition(null); - } - }.start(); + transition.setDuration(LayoutTransition.CHANGE_DISAPPEARING, 100); + + transition.setDuration(LayoutTransition.CHANGE_APPEARING, 100); + // CHANGE_APPEARING plays after CHANGE_APPEARING + transition.setStartDelay(LayoutTransition.APPEARING, 100); + + transition.setDuration(LayoutTransition.APPEARING, 100); + + return moveView(previousParent, newParentView, childView, index, params); + } + + @Override + public void postAnimation() { + previousParent.setLayoutTransition(null); + newParentView.setLayoutTransition(null); + } + }.start(); + } // always return success since the real status will come through the listener. return SUCCESS.createResult(layoutParams); @@ -707,7 +738,7 @@ public class RenderSessionImpl { * * @param previousParent the previous parent, still owning the child at the time of the call. * @param newParent the new parent - * @param view the view to move + * @param movedView the view to move * @param index the new location in the new parent * @param params an option (can be null) {@link LayoutParams} instance. * @@ -715,12 +746,12 @@ public class RenderSessionImpl { * {@link Status#ERROR_VIEWGROUP_NO_CHILDREN} if the given parent doesn't support * adding views. */ - private Result moveView(ViewGroup previousParent, final ViewGroup newParent, View view, - final int index, final LayoutParams params) { + private Result moveView(ViewGroup previousParent, final ViewGroup newParent, + final View movedView, final int index, final LayoutParams params) { try { // check if there is a transition on the previousParent. - LayoutTransition transition = previousParent.getLayoutTransition(); - if (transition != null) { + LayoutTransition previousTransition = previousParent.getLayoutTransition(); + if (previousTransition != null) { // in this case there is an animation. This means we have to wait for the child's // parent reference to be null'ed out so that we can add it to the new parent. // It is technically removed right before the DISAPPEARING animation is done (if @@ -730,48 +761,50 @@ public class RenderSessionImpl { // parent, we need to wait until the CHANGE_DISAPPEARING animation is done before // adding the child or the child will appear in its new location before the // other children have made room for it. - // If the parents are different, then we can add the child to its new parent right - // after the DISAPPEARING animation is done. - - final int waitForType = newParent == previousParent ? - LayoutTransition.CHANGE_DISAPPEARING : LayoutTransition.DISAPPEARING; // add a listener to the transition to be notified of the actual removal. - transition.addTransitionListener(new TransitionListener() { + previousTransition.addTransitionListener(new TransitionListener() { + private int mChangeDisappearingCount = 0; public void startTransition(LayoutTransition transition, ViewGroup container, View view, int transitionType) { - // don't care. + if (transitionType == LayoutTransition.CHANGE_DISAPPEARING) { + mChangeDisappearingCount++; + } } public void endTransition(LayoutTransition transition, ViewGroup container, View view, int transitionType) { - if (transitionType == waitForType) { + if (transitionType == LayoutTransition.CHANGE_DISAPPEARING) { + mChangeDisappearingCount--; + } + + if (transitionType == LayoutTransition.CHANGE_DISAPPEARING && + mChangeDisappearingCount == 0) { // add it to the parentView in the correct location if (params != null) { - newParent.addView(view, index, params); + newParent.addView(movedView, index, params); } else { - newParent.addView(view, index); + newParent.addView(movedView, index); } - } } }); // remove the view from the current parent. - previousParent.removeView(view); + previousParent.removeView(movedView); // and return since adding the view to the new parent is done in the listener. return SUCCESS.createResult(); } else { // standard code with no animation. pretty simple. - previousParent.removeView(view); + previousParent.removeView(movedView); // add it to the parentView in the correct location if (params != null) { - newParent.addView(view, index, params); + newParent.addView(movedView, index, params); } else { - newParent.addView(view, index); + newParent.addView(movedView, index); } return SUCCESS.createResult(); |