Merge "Clean up of the DatePicker"
diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java
index 6baf1c2..e403ac2 100644
--- a/core/java/android/content/res/CompatibilityInfo.java
+++ b/core/java/android/content/res/CompatibilityInfo.java
@@ -141,10 +141,16 @@
appFlags = appInfo.flags;
if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS) != 0) {
- mCompatibilityFlags |= LARGE_SCREENS | CONFIGURED_LARGE_SCREENS;
+ // Saying you support large screens also implies you support xlarge
+ // screens; there is no compatibility mode for a large app on an
+ // xlarge screen.
+ mCompatibilityFlags |= LARGE_SCREENS | CONFIGURED_LARGE_SCREENS
+ | XLARGE_SCREENS | CONFIGURED_XLARGE_SCREENS
+ | EXPANDABLE | CONFIGURED_EXPANDABLE;
}
if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS) != 0) {
- mCompatibilityFlags |= XLARGE_SCREENS | CONFIGURED_XLARGE_SCREENS;
+ mCompatibilityFlags |= XLARGE_SCREENS | CONFIGURED_XLARGE_SCREENS
+ | EXPANDABLE | CONFIGURED_EXPANDABLE;
}
if ((appInfo.flags & ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS) != 0) {
mCompatibilityFlags |= EXPANDABLE | CONFIGURED_EXPANDABLE;
diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java
index 0492fce..d143243 100644
--- a/core/java/android/net/TrafficStats.java
+++ b/core/java/android/net/TrafficStats.java
@@ -219,7 +219,7 @@
public static native long getUidTcpTxSegments(int uid);
/**
- * Get the number of TCP payload bytes received for this UID.
+ * Get the number of TCP segments received for this UID.
* Does not include TCP control packets (SYN/ACKs/FIN/..).
* The statistics are across all interfaces.
*
diff --git a/core/java/android/widget/TabWidget.java b/core/java/android/widget/TabWidget.java
index 1a4ff29..22f6f4e 100644
--- a/core/java/android/widget/TabWidget.java
+++ b/core/java/android/widget/TabWidget.java
@@ -66,6 +66,10 @@
private final Rect mBounds = new Rect();
+ // When positive, the widths and heights of tabs will be imposed so that they fit in parent
+ private int mImposedTabsHeight = -1;
+ private int[] mImposedTabWidths;
+
public TabWidget(Context context) {
this(context, null);
}
@@ -150,52 +154,62 @@
setOnFocusChangeListener(this);
}
- /**
- * {@inheritDoc}
- */
@Override
- void measureHorizontal(int widthMeasureSpec, int heightMeasureSpec) {
- // First measure with no constraint
- final int unspecifiedWidth = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
- super.measureHorizontal(unspecifiedWidth, heightMeasureSpec);
+ void measureChildBeforeLayout(View child, int childIndex,
+ int widthMeasureSpec, int totalWidth,
+ int heightMeasureSpec, int totalHeight) {
- final int count = getChildCount();
- int totalWidth = 0;
- int totalCount = 0;
- for (int i = 0; i < count; i++) {
- final View child = getChildAt(i);
- if (child.getVisibility() == GONE) {
- continue;
- }
- final int childWidth = child.getMeasuredWidth();
- totalWidth += childWidth;
- totalCount++;
+ if (mImposedTabsHeight >= 0) {
+ widthMeasureSpec = MeasureSpec.makeMeasureSpec(
+ totalWidth + mImposedTabWidths[childIndex], MeasureSpec.EXACTLY);
+ heightMeasureSpec = MeasureSpec.makeMeasureSpec(mImposedTabsHeight,
+ MeasureSpec.EXACTLY);
}
- final int width = MeasureSpec.getSize(widthMeasureSpec);
- if (totalWidth > width && totalCount > 0) {
- int extraWidth = totalWidth - width;
+ super.measureChildBeforeLayout(child, childIndex,
+ widthMeasureSpec, totalWidth, heightMeasureSpec, totalHeight);
+ }
+
+ @Override
+ void measureHorizontal(int widthMeasureSpec, int heightMeasureSpec) {
+ // First, measure with no constraint
+ final int unspecifiedWidth = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ super.measureHorizontal(unspecifiedWidth, heightMeasureSpec);
+ mImposedTabsHeight = -1;
+
+ int extraWidth = getMeasuredWidth() - MeasureSpec.getSize(widthMeasureSpec);
+ if (extraWidth > 0) {
+ final int count = getChildCount();
+
+ int childCount = 0;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
- if (child.getVisibility() == GONE) {
- continue;
- }
- final int childWidth = child.getMeasuredWidth();
- final int delta = extraWidth / totalCount;
- final int tabWidth = Math.max(0, childWidth - delta);
-
- final int tabWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
- tabWidth, MeasureSpec.EXACTLY);
- final int tabHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
- child.getMeasuredHeight(), MeasureSpec.EXACTLY);
-
- child.measure(tabWidthMeasureSpec, tabHeightMeasureSpec);
-
- // Make sure the extra width is evenly distributed, avoiding int division remainder
- extraWidth -= delta;
- totalCount--;
+ if (child.getVisibility() == GONE) continue;
+ childCount++;
}
- setMeasuredDimension(width, getMeasuredHeight());
+
+ if (childCount > 0) {
+ if (mImposedTabWidths == null || mImposedTabWidths.length != count) {
+ mImposedTabWidths = new int[count];
+ }
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() == GONE) continue;
+ final int childWidth = child.getMeasuredWidth();
+ final int delta = extraWidth / childCount;
+ final int newWidth = Math.max(0, childWidth - delta);
+ mImposedTabWidths[i] = newWidth;
+ // Make sure the extra width is evenly distributed, no int division remainder
+ extraWidth -= childWidth - newWidth; // delta may have been clamped
+ childCount--;
+ mImposedTabsHeight = Math.max(mImposedTabsHeight, child.getMeasuredHeight());
+ }
+ }
+ }
+
+ // Measure again, this time with imposed tab widths and respecting initial spec request
+ if (mImposedTabsHeight >= 0 || unspecifiedWidth != widthMeasureSpec) {
+ super.measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index e7c33ab..df933cb 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -5779,11 +5779,10 @@
int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
int unpaddedWidth = want;
+
+ if (mHorizontallyScrolling) want = VERY_WIDE;
+
int hintWant = want;
-
- if (mHorizontallyScrolling)
- want = VERY_WIDE;
-
int hintWidth = mHintLayout == null ? hintWant : mHintLayout.getWidth();
if (mLayout == null) {
diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java
index 4b37beb..18d1825 100644
--- a/core/java/android/widget/TimePicker.java
+++ b/core/java/android/widget/TimePicker.java
@@ -132,12 +132,8 @@
mHourSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() {
public void onValueChange(NumberPicker spinner, int oldVal, int newVal) {
if (!is24HourView()) {
- int minValue = mHourSpinner.getMinValue();
- int maxValue = mHourSpinner.getMaxValue();
- // toggle AM/PM if the spinner has wrapped and not in 24
- // format
- if ((oldVal == maxValue && newVal == minValue)
- || (oldVal == minValue && newVal == maxValue)) {
+ if ((oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY)
+ || (oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1)) {
mIsAm = !mIsAm;
updateAmPmControl();
}
@@ -163,21 +159,19 @@
int minValue = mMinuteSpinner.getMinValue();
int maxValue = mMinuteSpinner.getMaxValue();
if (oldVal == maxValue && newVal == minValue) {
- int currentHour = mHourSpinner.getValue();
- // toggle AM/PM if the spinner is about to wrap
- if (!is24HourView() && currentHour == mHourSpinner.getMaxValue()) {
+ int newHour = mHourSpinner.getValue() + 1;
+ if (!is24HourView() && newHour == HOURS_IN_HALF_DAY) {
mIsAm = !mIsAm;
updateAmPmControl();
}
- mHourSpinner.setValue(currentHour + 1);
+ mHourSpinner.setValue(newHour);
} else if (oldVal == minValue && newVal == maxValue) {
- int currentHour = mHourSpinner.getValue();
- // toggle AM/PM if the spinner is about to wrap
- if (!is24HourView() && currentHour == mHourSpinner.getMinValue()) {
+ int newHour = mHourSpinner.getValue() - 1;
+ if (!is24HourView() && newHour == HOURS_IN_HALF_DAY - 1) {
mIsAm = !mIsAm;
updateAmPmControl();
}
- mHourSpinner.setValue(currentHour - 1);
+ mHourSpinner.setValue(newHour);
}
onTimeChanged();
}
@@ -330,10 +324,12 @@
*/
public Integer getCurrentHour() {
int currentHour = mHourSpinner.getValue();
- if (is24HourView() || mIsAm) {
+ if (is24HourView()) {
return currentHour;
+ } else if (mIsAm) {
+ return currentHour % HOURS_IN_HALF_DAY;
} else {
- return (currentHour == HOURS_IN_HALF_DAY) ? 0 : currentHour + HOURS_IN_HALF_DAY;
+ return (currentHour % HOURS_IN_HALF_DAY) + HOURS_IN_HALF_DAY;
}
}
@@ -347,14 +343,16 @@
}
if (!is24HourView()) {
// convert [0,23] ordinal to wall clock display
- if (currentHour > HOURS_IN_HALF_DAY) {
- currentHour -= HOURS_IN_HALF_DAY;
+ if (currentHour >= HOURS_IN_HALF_DAY) {
mIsAm = false;
+ if (currentHour > HOURS_IN_HALF_DAY) {
+ currentHour = currentHour - HOURS_IN_HALF_DAY;
+ }
} else {
+ mIsAm = true;
if (currentHour == 0) {
currentHour = HOURS_IN_HALF_DAY;
}
- mIsAm = true;
}
updateAmPmControl();
}
diff --git a/core/java/com/android/internal/app/ActionBarImpl.java b/core/java/com/android/internal/app/ActionBarImpl.java
index b1b5d71..471a5a9 100644
--- a/core/java/com/android/internal/app/ActionBarImpl.java
+++ b/core/java/com/android/internal/app/ActionBarImpl.java
@@ -480,12 +480,12 @@
@Override
public void show() {
- if (mContainerView.getVisibility() == View.VISIBLE) {
- return;
- }
if (mCurrentAnim != null) {
mCurrentAnim.end();
}
+ if (mContainerView.getVisibility() == View.VISIBLE) {
+ return;
+ }
mContainerView.setVisibility(View.VISIBLE);
mContainerView.setAlpha(0);
AnimatorSet anim = new AnimatorSet();
diff --git a/core/res/res/raw/fallbackring.ogg b/core/res/res/raw/fallbackring.ogg
old mode 100644
new mode 100755
index 0cbf55d..a9adeb8
--- a/core/res/res/raw/fallbackring.ogg
+++ b/core/res/res/raw/fallbackring.ogg
Binary files differ
diff --git a/graphics/java/android/graphics/SurfaceTexture.java b/graphics/java/android/graphics/SurfaceTexture.java
index 4c659d4..1fc2722 100644
--- a/graphics/java/android/graphics/SurfaceTexture.java
+++ b/graphics/java/android/graphics/SurfaceTexture.java
@@ -32,6 +32,17 @@
* updated to contain the most recent image from the image stream. This may cause some frames of
* the stream to be skipped.
*
+ * <p>When sampling from the texture one should first transform the texture coordinates using the
+ * matrix queried via {@link #getTransformMatrix}. The transform matrix may change each time {@link
+ * #updateTexImage} is called, so it should be re-queried each time the texture image is updated.
+ * This matrix transforms traditional 2D OpenGL ES texture coordinate column vectors of the form (s,
+ * t, 0, 1) where s and t are on the inclusive interval [0, 1] to the proper sampling location in
+ * the streamed texture. This transform compensates for any properties of the image stream source
+ * that cause it to appear different from a traditional OpenGL ES texture. For example, sampling
+ * from the bottom left corner of the image can be accomplished by transforming the column vector
+ * (0, 0, 0, 1) using the queried matrix, while sampling from the top right corner of the image can
+ * be done by transforming (1, 1, 0, 1).
+ *
* <p>The texture object uses the GL_TEXTURE_EXTERNAL_OES texture target, which is defined by the
* OES_EGL_image_external OpenGL ES extension. This limits how the texture may be used.
*
diff --git a/libs/gui/SurfaceTexture.cpp b/libs/gui/SurfaceTexture.cpp
index 1dadd53..236ff4f 100644
--- a/libs/gui/SurfaceTexture.cpp
+++ b/libs/gui/SurfaceTexture.cpp
@@ -219,11 +219,19 @@
mSlots[mLastQueued].mEglImage = image;
mSlots[mLastQueued].mEglDisplay = dpy;
}
+
+ GLint error;
+ while ((error = glGetError()) != GL_NO_ERROR) {
+ LOGE("GL error cleared before updating SurfaceTexture: %#04x", error);
+ }
glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, (GLeglImageOES)image);
- GLint error = glGetError();
- if (error != GL_NO_ERROR) {
+ bool failed = false;
+ while ((error = glGetError()) != GL_NO_ERROR) {
LOGE("error binding external texture image %p (slot %d): %#04x",
image, mLastQueued, error);
+ failed = true;
+ }
+ if (failed) {
return -EINVAL;
}
diff --git a/opengl/libagl/egl.cpp b/opengl/libagl/egl.cpp
index 7ac6f92..a1cb23a 100644
--- a/opengl/libagl/egl.cpp
+++ b/opengl/libagl/egl.cpp
@@ -82,6 +82,11 @@
if (ggl_unlikely(gEGLErrorKey == -1))
return EGL_SUCCESS;
GLint error = (GLint)pthread_getspecific(gEGLErrorKey);
+ if (error == 0) {
+ // The TLS key has been created by another thread, but the value for
+ // this thread has not been initialized.
+ return EGL_SUCCESS;
+ }
pthread_setspecific(gEGLErrorKey, (void*)EGL_SUCCESS);
return error;
}
diff --git a/opengl/libs/EGL/egl.cpp b/opengl/libs/EGL/egl.cpp
index 8977fbf..3dc8c03f 100644
--- a/opengl/libs/EGL/egl.cpp
+++ b/opengl/libs/EGL/egl.cpp
@@ -389,10 +389,9 @@
}
static inline void clearError() {
- if (gEGLThreadLocalStorageKey != -1) {
- tls_t* tls = getTLS();
- tls->error = EGL_SUCCESS;
- }
+ // This must clear the error from all the underlying EGL implementations as
+ // well as the EGL wrapper layer.
+ eglGetError();
}
template<typename T>
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java
index c7968a4..8d7f016 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java
@@ -27,6 +27,8 @@
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
+import java.util.ArrayList;
+
/**
* This method adapter rewrites a method by discarding the original code and generating
* a call to a delegate. Original annotations are passed along unchanged.
@@ -124,7 +126,7 @@
public void generateCode() {
/*
* The goal is to generate a call to a static delegate method.
- * If this method is not-static, the first parameter will be this.
+ * If this method is non-static, the first parameter will be 'this'.
* All the parameters must be passed and then the eventual return type returned.
*
* Example, let's say we have a method such as
@@ -133,9 +135,19 @@
* We'll want to create a body that calls a delegate method like this:
* TheClass_Delegate.method_1(this, a, b, c);
*
+ * If the method is non-static and the class name is an inner class (e.g. has $ in its
+ * last segment), we want to push the 'this' of the outer class first:
+ * OuterClass_InnerClass_Delegate.method_1(
+ * OuterClass.this,
+ * OuterClass$InnerClass.this,
+ * a, b, c);
+ *
+ * Only one level of inner class is supported right now, for simplicity and because
+ * we don't need more.
+ *
* The generated class name is the current class name with "_Delegate" appended to it.
* One thing to realize is that we don't care about generics -- since generic types
- * are erased at runtime, they have no influence on the method being called.
+ * are erased at runtime, they have no influence on the method name being called.
*/
// Add our annotation
@@ -151,34 +163,61 @@
mVisitCodeCalled = true;
}
- int numVars = 0;
+ ArrayList<Type> paramTypes = new ArrayList<Type>();
+ String delegateClassName = mClassName + DELEGATE_SUFFIX;
+ boolean pushedArg0 = false;
+ int maxStack = 0;
- // Push "this" for an instance method, which is always ALOAD 0
+ // For an instance method (e.g. non-static), push the 'this' preceded
+ // by the 'this' of any outer class, if any.
if (!mIsStatic) {
- mParentVisitor.visitVarInsn(Opcodes.ALOAD, numVars++);
+ // Check if the last segment of the class name has inner an class.
+ // Right now we only support one level of inner classes.
+ int slash = mClassName.lastIndexOf('/');
+ int dol = mClassName.lastIndexOf('$');
+ if (dol != -1 && dol > slash && dol == mClassName.indexOf('$')) {
+ String outerClass = mClassName.substring(0, dol);
+ Type outerType = Type.getObjectType(outerClass);
+
+ // Change a delegate class name to "com/foo/Outer_Inner_Delegate"
+ delegateClassName = delegateClassName.replace('$', '_');
+
+ // The first-level inner class has a package-protected member called 'this$0'
+ // that points to the outer class.
+
+ // Push this.getField("this$0") on the call stack.
+ mParentVisitor.visitVarInsn(Opcodes.ALOAD, 0); // var 0 = this
+ mParentVisitor.visitFieldInsn(Opcodes.GETFIELD,
+ mClassName, // class where the field is defined
+ "this$0", // field name
+ outerType.getDescriptor()); // type of the field
+ maxStack++;
+ paramTypes.add(outerType);
+ }
+
+ // Push "this" for the instance method, which is always ALOAD 0
+ mParentVisitor.visitVarInsn(Opcodes.ALOAD, 0);
+ maxStack++;
+ pushedArg0 = true;
+ paramTypes.add(Type.getObjectType(mClassName));
}
- // Push all other arguments
+ // Push all other arguments. Start at arg 1 if we already pushed 'this' above.
Type[] argTypes = Type.getArgumentTypes(mDesc);
+ int maxLocals = pushedArg0 ? 1 : 0;
for (Type t : argTypes) {
int size = t.getSize();
- mParentVisitor.visitVarInsn(t.getOpcode(Opcodes.ILOAD), numVars);
- numVars += size;
+ mParentVisitor.visitVarInsn(t.getOpcode(Opcodes.ILOAD), maxLocals);
+ maxLocals += size;
+ maxStack += size;
+ paramTypes.add(t);
}
- // Construct the descriptor of the delegate. For a static method, it's the same
- // however for an instance method we need to pass the 'this' reference first
- String desc = mDesc;
- if (!mIsStatic) {
- Type[] argTypes2 = new Type[argTypes.length + 1];
-
- argTypes2[0] = Type.getObjectType(mClassName);
- System.arraycopy(argTypes, 0, argTypes2, 1, argTypes.length);
-
- desc = Type.getMethodDescriptor(Type.getReturnType(mDesc), argTypes2);
- }
-
- String delegateClassName = mClassName + DELEGATE_SUFFIX;
+ // Construct the descriptor of the delegate based on the parameters
+ // we pushed on the call stack. The return type remains unchanged.
+ String desc = Type.getMethodDescriptor(
+ Type.getReturnType(mDesc),
+ paramTypes.toArray(new Type[paramTypes.size()]));
// Invoke the static delegate
mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
@@ -189,7 +228,7 @@
Type returnType = Type.getReturnType(mDesc);
mParentVisitor.visitInsn(returnType.getOpcode(Opcodes.IRETURN));
- mParentVisitor.visitMaxs(numVars, numVars);
+ mParentVisitor.visitMaxs(maxStack, maxLocals);
mParentVisitor.visitEnd();
// For debugging now. Maybe we should collect these and store them in
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
index 7d80796..e8b3ea8 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
@@ -24,25 +24,38 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import com.android.tools.layoutlib.create.dataclass.ClassWithNative;
+import com.android.tools.layoutlib.create.dataclass.OuterClass;
+import com.android.tools.layoutlib.create.dataclass.OuterClass.InnerClass;
+
import org.junit.Before;
import org.junit.Test;
import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
import java.lang.annotation.Annotation;
+import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
+import java.util.HashMap;
import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
public class DelegateClassAdapterTest {
private MockLog mLog;
- private static final String CLASS_NAME =
- DelegateClassAdapterTest.class.getCanonicalName() + "$" +
- ClassWithNative.class.getSimpleName();
+ private static final String NATIVE_CLASS_NAME = ClassWithNative.class.getCanonicalName();
+ private static final String OUTER_CLASS_NAME = OuterClass.class.getCanonicalName();
+ private static final String INNER_CLASS_NAME = OuterClass.class.getCanonicalName() + "$" +
+ InnerClass.class.getSimpleName();
@Before
public void setUp() throws Exception {
@@ -55,11 +68,11 @@
*/
@SuppressWarnings("unchecked")
@Test
- public void testNoOp() throws Exception {
+ public void testNoOp() throws Throwable {
// create an instance of the class that will be modified
// (load the class in a distinct class loader so that we can trash its definition later)
ClassLoader cl1 = new ClassLoader(this.getClass().getClassLoader()) { };
- Class<ClassWithNative> clazz1 = (Class<ClassWithNative>) cl1.loadClass(CLASS_NAME);
+ Class<ClassWithNative> clazz1 = (Class<ClassWithNative>) cl1.loadClass(NATIVE_CLASS_NAME);
ClassWithNative instance1 = clazz1.newInstance();
assertEquals(42, instance1.add(20, 22));
try {
@@ -73,42 +86,47 @@
ClassWriter cw = new ClassWriter(0 /*flags*/);
HashSet<String> delegateMethods = new HashSet<String>();
- String internalClassName = CLASS_NAME.replace('.', '/');
+ String internalClassName = NATIVE_CLASS_NAME.replace('.', '/');
DelegateClassAdapter cv = new DelegateClassAdapter(
mLog, cw, internalClassName, delegateMethods);
- ClassReader cr = new ClassReader(CLASS_NAME);
+ ClassReader cr = new ClassReader(NATIVE_CLASS_NAME);
cr.accept(cv, 0 /* flags */);
// Load the generated class in a different class loader and try it again
- final byte[] bytes = cw.toByteArray();
- ClassLoader2 cl2 = new ClassLoader2(bytes) {
- @Override
- public void testModifiedInstance() throws Exception {
- Class<?> clazz2 = loadClass(CLASS_NAME);
- Object i2 = clazz2.newInstance();
- assertNotNull(i2);
- assertEquals(42, callAdd(i2, 20, 22));
+ ClassLoader2 cl2 = null;
+ try {
+ cl2 = new ClassLoader2() {
+ @Override
+ public void testModifiedInstance() throws Exception {
+ Class<?> clazz2 = loadClass(NATIVE_CLASS_NAME);
+ Object i2 = clazz2.newInstance();
+ assertNotNull(i2);
+ assertEquals(42, callAdd(i2, 20, 22));
- try {
- callCallNativeInstance(i2, 10, 3.1415, new Object[0]);
- fail("Test should have failed to invoke callTheNativeMethod [2]");
- } catch (InvocationTargetException e) {
- // This is expected to fail since the native method has NOT been
- // overridden here.
- assertEquals(UnsatisfiedLinkError.class, e.getCause().getClass());
+ try {
+ callCallNativeInstance(i2, 10, 3.1415, new Object[0]);
+ fail("Test should have failed to invoke callTheNativeMethod [2]");
+ } catch (InvocationTargetException e) {
+ // This is expected to fail since the native method has NOT been
+ // overridden here.
+ assertEquals(UnsatisfiedLinkError.class, e.getCause().getClass());
+ }
+
+ // Check that the native method does NOT have the new annotation
+ Method[] m = clazz2.getDeclaredMethods();
+ assertEquals("native_instance", m[2].getName());
+ assertTrue(Modifier.isNative(m[2].getModifiers()));
+ Annotation[] a = m[2].getAnnotations();
+ assertEquals(0, a.length);
}
-
- // Check that the native method does NOT have the new annotation
- Method[] m = clazz2.getDeclaredMethods();
- assertEquals("native_instance", m[2].getName());
- assertTrue(Modifier.isNative(m[2].getModifiers()));
- Annotation[] a = m[2].getAnnotations();
- assertEquals(0, a.length);
- }
- };
- cl2.testModifiedInstance();
+ };
+ cl2.add(NATIVE_CLASS_NAME, cw);
+ cl2.testModifiedInstance();
+ } catch (Throwable t) {
+ throw dumpGeneratedClass(t, cl2);
+ }
}
/**
@@ -122,38 +140,37 @@
public void testConstructorsNotSupported() throws IOException {
ClassWriter cw = new ClassWriter(0 /*flags*/);
- String internalClassName = CLASS_NAME.replace('.', '/');
+ String internalClassName = NATIVE_CLASS_NAME.replace('.', '/');
HashSet<String> delegateMethods = new HashSet<String>();
delegateMethods.add("<init>");
DelegateClassAdapter cv = new DelegateClassAdapter(
mLog, cw, internalClassName, delegateMethods);
- ClassReader cr = new ClassReader(CLASS_NAME);
+ ClassReader cr = new ClassReader(NATIVE_CLASS_NAME);
cr.accept(cv, 0 /* flags */);
}
@Test
- public void testDelegateNative() throws Exception {
+ public void testDelegateNative() throws Throwable {
ClassWriter cw = new ClassWriter(0 /*flags*/);
- String internalClassName = CLASS_NAME.replace('.', '/');
+ String internalClassName = NATIVE_CLASS_NAME.replace('.', '/');
HashSet<String> delegateMethods = new HashSet<String>();
delegateMethods.add(DelegateClassAdapter.ALL_NATIVES);
DelegateClassAdapter cv = new DelegateClassAdapter(
mLog, cw, internalClassName, delegateMethods);
- ClassReader cr = new ClassReader(CLASS_NAME);
+ ClassReader cr = new ClassReader(NATIVE_CLASS_NAME);
cr.accept(cv, 0 /* flags */);
// Load the generated class in a different class loader and try it
- final byte[] bytes = cw.toByteArray();
-
+ ClassLoader2 cl2 = null;
try {
- ClassLoader2 cl2 = new ClassLoader2(bytes) {
+ cl2 = new ClassLoader2() {
@Override
public void testModifiedInstance() throws Exception {
- Class<?> clazz2 = loadClass(CLASS_NAME);
+ Class<?> clazz2 = loadClass(NATIVE_CLASS_NAME);
Object i2 = clazz2.newInstance();
assertNotNull(i2);
@@ -173,48 +190,105 @@
assertEquals("LayoutlibDelegate", a[0].annotationType().getSimpleName());
}
};
+ cl2.add(NATIVE_CLASS_NAME, cw);
cl2.testModifiedInstance();
+ } catch (Throwable t) {
+ throw dumpGeneratedClass(t, cl2);
+ }
+ }
- // This code block is useful for debugging. However to make it work you need to
- // pull in the org.objectweb.asm.util.TraceClassVisitor class and associated
- // utilities which are found in the ASM source jar.
- //
- // } catch (Throwable t) {
- // For debugging, dump the bytecode of the class in case of unexpected error.
- // StringWriter sw = new StringWriter();
- // PrintWriter pw = new PrintWriter(sw);
- // TraceClassVisitor tcv = new TraceClassVisitor(pw);
- // ClassReader cr2 = new ClassReader(bytes);
- // cr2.accept(tcv, 0 /* flags */);
- // String msg = "\n" + t.getClass().getCanonicalName();
- // if (t.getMessage() != null) {
- // msg += ": " + t.getMessage();
- // }
- // msg = msg + "\nBytecode dump:\n" + sw.toString();
- // // Re-throw exception with new message
- // RuntimeException ex = new RuntimeException(msg, t);
- // throw ex;
- } finally {
+ @Test
+ public void testDelegateInner() throws Throwable {
+ // We'll delegate the "get" method of both the inner and outer class.
+ HashSet<String> delegateMethods = new HashSet<String>();
+ delegateMethods.add("get");
+
+ // Generate the delegate for the outer class.
+ ClassWriter cwOuter = new ClassWriter(0 /*flags*/);
+ String outerClassName = OUTER_CLASS_NAME.replace('.', '/');
+ DelegateClassAdapter cvOuter = new DelegateClassAdapter(
+ mLog, cwOuter, outerClassName, delegateMethods);
+ ClassReader cr = new ClassReader(OUTER_CLASS_NAME);
+ cr.accept(cvOuter, 0 /* flags */);
+
+ // Generate the delegate for the inner class.
+ ClassWriter cwInner = new ClassWriter(0 /*flags*/);
+ String innerClassName = INNER_CLASS_NAME.replace('.', '/');
+ DelegateClassAdapter cvInner = new DelegateClassAdapter(
+ mLog, cwInner, innerClassName, delegateMethods);
+ cr = new ClassReader(INNER_CLASS_NAME);
+ cr.accept(cvInner, 0 /* flags */);
+
+ // Load the generated classes in a different class loader and try them
+ ClassLoader2 cl2 = null;
+ try {
+ cl2 = new ClassLoader2() {
+ @Override
+ public void testModifiedInstance() throws Exception {
+
+ // Check the outer class
+ Class<?> outerClazz2 = loadClass(OUTER_CLASS_NAME);
+ Object o2 = outerClazz2.newInstance();
+ assertNotNull(o2);
+
+ // The original Outer.get returns 1+10+20,
+ // but the delegate makes it return 4+10+20
+ assertEquals(4+10+20, callGet(o2, 10, 20));
+
+ // Check the inner class. Since it's not a static inner class, we need
+ // to use the hidden constructor that takes the outer class as first parameter.
+ Class<?> innerClazz2 = loadClass(INNER_CLASS_NAME);
+ Constructor<?> innerCons = innerClazz2.getConstructor(
+ new Class<?>[] { outerClazz2 });
+ Object i2 = innerCons.newInstance(new Object[] { o2 });
+ assertNotNull(i2);
+
+ // The original Inner.get returns 3+10+20,
+ // but the delegate makes it return 6+10+20
+ assertEquals(6+10+20, callGet(i2, 10, 20));
+ }
+ };
+ cl2.add(OUTER_CLASS_NAME, cwOuter.toByteArray());
+ cl2.add(INNER_CLASS_NAME, cwInner.toByteArray());
+ cl2.testModifiedInstance();
+ } catch (Throwable t) {
+ throw dumpGeneratedClass(t, cl2);
}
}
//-------
/**
- * A class loader than can define and instantiate our dummy {@link ClassWithNative}.
+ * A class loader than can define and instantiate our modified classes.
* <p/>
- * The trick here is that this class loader will test our modified version of ClassWithNative.
+ * The trick here is that this class loader will test our <em>modified</em> version
+ * of the classes, the one with the delegate calls.
+ * <p/>
* Trying to do so in the original class loader generates all sort of link issues because
* there are 2 different definitions of the same class name. This class loader will
* define and load the class when requested by name and provide helpers to access the
* instance methods via reflection.
*/
private abstract class ClassLoader2 extends ClassLoader {
- private final byte[] mClassWithNative;
- public ClassLoader2(byte[] classWithNative) {
+ private final Map<String, byte[]> mClassDefs = new HashMap<String, byte[]>();
+
+ public ClassLoader2() {
super(null);
- mClassWithNative = classWithNative;
+ }
+
+ public ClassLoader2 add(String className, byte[] definition) {
+ mClassDefs.put(className, definition);
+ return this;
+ }
+
+ public ClassLoader2 add(String className, ClassWriter rewrittenClass) {
+ mClassDefs.put(className, rewrittenClass.toByteArray());
+ return this;
+ }
+
+ private Set<Entry<String, byte[]>> getByteCode() {
+ return mClassDefs.entrySet();
}
@SuppressWarnings("unused")
@@ -224,9 +298,10 @@
return super.findClass(name);
} catch (ClassNotFoundException e) {
- if (CLASS_NAME.equals(name)) {
+ byte[] def = mClassDefs.get(name);
+ if (def != null) {
// Load the modified ClassWithNative from its bytes representation.
- return defineClass(CLASS_NAME, mClassWithNative, 0, mClassWithNative.length);
+ return defineClass(name, def, 0, def.length);
}
try {
@@ -244,6 +319,17 @@
}
/**
+ * Accesses {@link OuterClass#get()} or {@link InnerClass#get() }via reflection.
+ */
+ public int callGet(Object instance, int a, long b) throws Exception {
+ Method m = instance.getClass().getMethod("get",
+ new Class<?>[] { int.class, long.class } );
+
+ Object result = m.invoke(instance, new Object[] { a, b });
+ return ((Integer) result).intValue();
+ }
+
+ /**
* Accesses {@link ClassWithNative#add(int, int)} via reflection.
*/
public int callAdd(Object instance, int a, int b) throws Exception {
@@ -271,34 +357,53 @@
}
/**
- * Dummy test class with a native method.
- * The native method is not defined and any attempt to invoke it will
- * throw an {@link UnsatisfiedLinkError}.
+ * For debugging, it's useful to dump the content of the generated classes
+ * along with the exception that was generated.
+ *
+ * However to make it work you need to pull in the org.objectweb.asm.util.TraceClassVisitor
+ * class and associated utilities which are found in the ASM source jar. Since we don't
+ * want that dependency in the source code, we only put it manually for development and
+ * access the TraceClassVisitor via reflection if present.
+ *
+ * @param t The exception thrown by {@link ClassLoader2#testModifiedInstance()}
+ * @param cl2 The {@link ClassLoader2} instance with the generated bytecode.
+ * @return Either original {@code t} or a new wrapper {@link Throwable}
*/
- public static class ClassWithNative {
- public ClassWithNative() {
- }
+ private Throwable dumpGeneratedClass(Throwable t, ClassLoader2 cl2) {
+ try {
+ // For debugging, dump the bytecode of the class in case of unexpected error
+ // if we can find the TraceClassVisitor class.
+ Class<?> tcvClass = Class.forName("org.objectweb.asm.util.TraceClassVisitor");
- public int add(int a, int b) {
- return a + b;
- }
+ StringBuilder sb = new StringBuilder();
+ sb.append('\n').append(t.getClass().getCanonicalName());
+ if (t.getMessage() != null) {
+ sb.append(": ").append(t.getMessage());
+ }
- public int callNativeInstance(int a, double d, Object[] o) {
- return native_instance(a, d, o);
- }
+ for (Entry<String, byte[]> entry : cl2.getByteCode()) {
+ String className = entry.getKey();
+ byte[] bytes = entry.getValue();
- private native int native_instance(int a, double d, Object[] o);
- }
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ // next 2 lines do: TraceClassVisitor tcv = new TraceClassVisitor(pw);
+ Constructor<?> cons = tcvClass.getConstructor(new Class<?>[] { pw.getClass() });
+ Object tcv = cons.newInstance(new Object[] { pw });
+ ClassReader cr2 = new ClassReader(bytes);
+ cr2.accept((ClassVisitor) tcv, 0 /* flags */);
- /**
- * The delegate that receives the call to {@link ClassWithNative}'s overridden methods.
- */
- public static class ClassWithNative_Delegate {
- public static int native_instance(ClassWithNative instance, int a, double d, Object[] o) {
- if (o != null && o.length > 0) {
- o[0] = instance;
+ sb.append("\nBytecode dump: <").append(className).append(">:\n")
+ .append(sw.toString());
}
- return (int)(a + d);
+
+ // Re-throw exception with new message
+ RuntimeException ex = new RuntimeException(sb.toString(), t);
+ return ex;
+ } catch (Throwable ignore) {
+ // In case of problem, just throw the original exception as-is.
+ return t;
}
}
+
}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/ClassWithNative.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/ClassWithNative.java
new file mode 100644
index 0000000..c314853
--- /dev/null
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/ClassWithNative.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.layoutlib.create.dataclass;
+
+import com.android.tools.layoutlib.create.DelegateClassAdapterTest;
+
+/**
+ * Dummy test class with a native method.
+ * The native method is not defined and any attempt to invoke it will
+ * throw an {@link UnsatisfiedLinkError}.
+ *
+ * Used by {@link DelegateClassAdapterTest}.
+ */
+public class ClassWithNative {
+ public ClassWithNative() {
+ }
+
+ public int add(int a, int b) {
+ return a + b;
+ }
+
+ // Note: it's good to have a long or double for testing parameters since they take
+ // 2 slots in the stack/locals maps.
+
+ public int callNativeInstance(int a, double d, Object[] o) {
+ return native_instance(a, d, o);
+ }
+
+ private native int native_instance(int a, double d, Object[] o);
+}
+
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/ClassWithNative_Delegate.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/ClassWithNative_Delegate.java
new file mode 100644
index 0000000..a3d4dc6
--- /dev/null
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/ClassWithNative_Delegate.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.layoutlib.create.dataclass;
+
+import com.android.tools.layoutlib.create.DelegateClassAdapterTest;
+
+/**
+ * The delegate that receives the call to {@link ClassWithNative_Delegate}'s overridden methods.
+ *
+ * Used by {@link DelegateClassAdapterTest}.
+ */
+public class ClassWithNative_Delegate {
+ public static int native_instance(ClassWithNative instance, int a, double d, Object[] o) {
+ if (o != null && o.length > 0) {
+ o[0] = instance;
+ }
+ return (int)(a + d);
+ }
+}
+
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java
new file mode 100644
index 0000000..9dc2f69
--- /dev/null
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java
@@ -0,0 +1,48 @@
+/*
+ * 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 com.android.tools.layoutlib.create.dataclass;
+
+import com.android.tools.layoutlib.create.DelegateClassAdapterTest;
+
+/**
+ * Test class with an inner class.
+ *
+ * Used by {@link DelegateClassAdapterTest}.
+ */
+public class OuterClass {
+ private int mOuterValue = 1;
+ public OuterClass() {
+ }
+
+ // Outer.get returns 1 + a + b
+ // Note: it's good to have a long or double for testing parameters since they take
+ // 2 slots in the stack/locals maps.
+ public int get(int a, long b) {
+ return mOuterValue + a + (int) b;
+ }
+
+ public class InnerClass {
+ public InnerClass() {
+ }
+
+ // Inner.get returns 1+2=3 + a + b
+ public int get(int a, long b) {
+ return 2 + mOuterValue + a + (int) b;
+ }
+ }
+}
+
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_Delegate.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_Delegate.java
new file mode 100644
index 0000000..3252d87
--- /dev/null
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_Delegate.java
@@ -0,0 +1,30 @@
+/*
+ * 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 com.android.tools.layoutlib.create.dataclass;
+
+import com.android.tools.layoutlib.create.DelegateClassAdapterTest;
+
+/**
+ * Used by {@link DelegateClassAdapterTest}.
+ */
+public class OuterClass_Delegate {
+ // The delegate override of Outer.get returns 4 + a + b
+ public static int get(OuterClass instance, int a, long b) {
+ return 4 + a + (int) b;
+ }
+}
+
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_InnerClass_Delegate.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_InnerClass_Delegate.java
new file mode 100644
index 0000000..b472220
--- /dev/null
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_InnerClass_Delegate.java
@@ -0,0 +1,30 @@
+/*
+ * 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 com.android.tools.layoutlib.create.dataclass;
+
+import com.android.tools.layoutlib.create.DelegateClassAdapterTest;
+import com.android.tools.layoutlib.create.dataclass.OuterClass.InnerClass;
+
+/**
+ * Used by {@link DelegateClassAdapterTest}.
+ */
+public class OuterClass_InnerClass_Delegate {
+ // The delegate override of Inner.get return 6 + a + b
+ public static int get(OuterClass outer, InnerClass inner, int a, long b) {
+ return 6 + a + (int) b;
+ }
+}