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;
+    }
+}