Support databinding in listitem layouts.

Add a wrapper around the PullParser to support stripping out databinding
parts.

Bug: http://b.android.com/187428
Change-Id: I88080d8f4108cb5ae27a137ad20c5dd7d516f3ea
diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java b/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java
index 163fbcb..7a69a06 100644
--- a/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java
+++ b/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java
@@ -404,7 +404,7 @@
                     if (xml.isFile()) {
                         // we need to create a pull parser around the layout XML file, and then
                         // give that to our XmlBlockParser
-                        parser = ParserFactory.create(xml);
+                        parser = ParserFactory.create(xml, true);
                     }
                 }
 
diff --git a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
index f1726eb..5db1bde 100644
--- a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
+++ b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
@@ -206,7 +206,7 @@
                 File f = new File(value.getValue());
                 if (f.isFile()) {
                     try {
-                        XmlPullParser parser = ParserFactory.create(f);
+                        XmlPullParser parser = ParserFactory.create(f, true);
 
                         BridgeXmlBlockParser bridgeParser = new BridgeXmlBlockParser(
                                 parser, bridgeContext, value.isFramework());
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 689e359..b2dc29a 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
@@ -436,7 +436,7 @@
                 // we need to create a pull parser around the layout XML file, and then
                 // give that to our XmlBlockParser
                 try {
-                    XmlPullParser parser = ParserFactory.create(xml);
+                    XmlPullParser parser = ParserFactory.create(xml, true);
 
                     // set the resource ref to have correct view cookies
                     mBridgeInflater.setResourceReference(resource);
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/LayoutParserWrapper.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/LayoutParserWrapper.java
new file mode 100644
index 0000000..3d2a238
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/LayoutParserWrapper.java
@@ -0,0 +1,378 @@
+/*
+ * Copyright (C) 2015 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.layoutlib.bridge.impl;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.annotation.Nullable;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A wrapper around XmlPullParser that can peek forward to inspect if the file is a data-binding
+ * layout and some parts need to be stripped.
+ */
+public class LayoutParserWrapper implements XmlPullParser {
+
+    // Data binding constants.
+    private static final String TAG_LAYOUT = "layout";
+    private static final String TAG_DATA = "data";
+    private static final String DEFAULT = "default=";
+
+    private final XmlPullParser mDelegate;
+
+    // Storage for peeked values.
+    private boolean mPeeked;
+    private int mEventType;
+    private int mDepth;
+    private int mNext;
+    private List<Attribute> mAttributes;
+    private String mText;
+    private String mName;
+
+    // Used to end the document before the actual parser ends.
+    private int mFinalDepth = -1;
+    private boolean mEndNow;
+
+    public LayoutParserWrapper(XmlPullParser delegate) {
+        mDelegate = delegate;
+    }
+
+    public LayoutParserWrapper peekTillLayoutStart() throws IOException, XmlPullParserException {
+        final int STATE_LAYOUT_NOT_STARTED = 0;  // <layout> tag not encountered yet.
+        final int STATE_ROOT_NOT_STARTED = 1;    // the main view root not found yet.
+        final int STATE_INSIDE_DATA = 2;         // START_TAG for <data> found, but not END_TAG.
+
+        int state = STATE_LAYOUT_NOT_STARTED;
+        int dataDepth = -1;    // depth of the <data> tag. Should be two.
+        while (true) {
+            int peekNext = peekNext();
+            switch (peekNext) {
+                case START_TAG:
+                    if (state == STATE_LAYOUT_NOT_STARTED) {
+                        if (mName.equals(TAG_LAYOUT)) {
+                            state = STATE_ROOT_NOT_STARTED;
+                        } else {
+                            return this; // no layout tag in the file.
+                        }
+                    } else if (state == STATE_ROOT_NOT_STARTED) {
+                        if (mName.equals(TAG_DATA)) {
+                            state = STATE_INSIDE_DATA;
+                            dataDepth = mDepth;
+                        } else {
+                            mFinalDepth = mDepth;
+                            return this;
+                        }
+                    }
+                    break;
+                case END_TAG:
+                    if (state == STATE_INSIDE_DATA) {
+                        if (mDepth <= dataDepth) {
+                            state = STATE_ROOT_NOT_STARTED;
+                        }
+                    }
+                    break;
+                case END_DOCUMENT:
+                    // No layout start found.
+                    return this;
+            }
+            // consume the peeked tag.
+            next();
+        }
+    }
+
+    private int peekNext() throws IOException, XmlPullParserException {
+        if (mPeeked) {
+            return mNext;
+        }
+        mEventType = mDelegate.getEventType();
+        mNext = mDelegate.next();
+        if (mEventType == START_TAG) {
+            int count = mDelegate.getAttributeCount();
+            mAttributes = count > 0 ? new ArrayList<Attribute>(count) :
+                    Collections.<Attribute>emptyList();
+            for (int i = 0; i < count; i++) {
+                mAttributes.add(new Attribute(mDelegate.getAttributeNamespace(i),
+                        mDelegate.getAttributeName(i), mDelegate.getAttributeValue(i)));
+            }
+        }
+        mDepth = mDelegate.getDepth();
+        mText = mDelegate.getText();
+        mName = mDelegate.getName();
+        mPeeked = true;
+        return mNext;
+    }
+
+    private void reset() {
+        mAttributes = null;
+        mText = null;
+        mName = null;
+        mPeeked = false;
+    }
+
+    @Override
+    public int next() throws XmlPullParserException, IOException {
+        int returnValue;
+        int depth;
+        if (mPeeked) {
+            returnValue = mNext;
+            depth = mDepth;
+            reset();
+        } else if (mEndNow) {
+            return END_DOCUMENT;
+        } else {
+            returnValue = mDelegate.next();
+            depth = getDepth();
+        }
+        if (returnValue == END_TAG && depth <= mFinalDepth) {
+            mEndNow = true;
+        }
+        return returnValue;
+    }
+
+    @Override
+    public int getEventType() throws XmlPullParserException {
+        return mPeeked ? mEventType : mDelegate.getEventType();
+    }
+
+    @Override
+    public int getDepth() {
+        return mPeeked ? mDepth : mDelegate.getDepth();
+    }
+
+    @Override
+    public String getName() {
+        return mPeeked ? mName : mDelegate.getName();
+    }
+
+    @Override
+    public String getText() {
+        return mPeeked ? mText : mDelegate.getText();
+    }
+
+    @Override
+    public String getAttributeValue(@Nullable String namespace, String name) {
+        String returnValue = null;
+        if (mPeeked) {
+            if (mAttributes == null) {
+                if (mEventType != START_TAG) {
+                    throw new IndexOutOfBoundsException("getAttributeValue() called when not at " +
+                            "START_TAG.");
+                } else {
+                    return null;
+                }
+            } else {
+                for (Attribute attribute : mAttributes) {
+                    //noinspection StringEquality for nullness check.
+                    if (attribute.name.equals(name) && (attribute.namespace == namespace ||
+                            attribute.namespace != null && attribute.namespace.equals(namespace))) {
+                        returnValue = attribute.value;
+                        break;
+                    }
+                }
+            }
+        } else {
+            returnValue = mDelegate.getAttributeValue(namespace, name);
+        }
+        // Check if the value is bound via data-binding, if yes get the default value.
+        if (returnValue != null && mFinalDepth >= 0 && returnValue.startsWith("@{")) {
+            // TODO: Improve the detection of default keyword.
+            int i = returnValue.lastIndexOf(DEFAULT);
+            return i > 0 ? returnValue.substring(i + DEFAULT.length(), returnValue.length() - 1)
+                    : null;
+        }
+        return returnValue;
+    }
+
+    private static class Attribute {
+        @Nullable
+        public final String namespace;
+        public final String name;
+        public final String value;
+
+        public Attribute(@Nullable String namespace, String name, String value) {
+            this.namespace = namespace;
+            this.name = name;
+            this.value = value;
+        }
+    }
+
+    // Not affected by peeking.
+
+    @Override
+    public void setFeature(String s, boolean b) throws XmlPullParserException {
+        mDelegate.setFeature(s, b);
+    }
+
+    @Override
+    public void setProperty(String s, Object o) throws XmlPullParserException {
+        mDelegate.setProperty(s, o);
+    }
+
+    @Override
+    public void setInput(InputStream inputStream, String s) throws XmlPullParserException {
+        mDelegate.setInput(inputStream, s);
+    }
+
+    @Override
+    public void setInput(Reader reader) throws XmlPullParserException {
+        mDelegate.setInput(reader);
+    }
+
+    @Override
+    public String getInputEncoding() {
+        return mDelegate.getInputEncoding();
+    }
+
+    @Override
+    public String getNamespace(String s) {
+        return mDelegate.getNamespace(s);
+    }
+
+    @Override
+    public String getPositionDescription() {
+        return mDelegate.getPositionDescription();
+    }
+
+    @Override
+    public int getLineNumber() {
+        return mDelegate.getLineNumber();
+    }
+
+    @Override
+    public String getNamespace() {
+        return mDelegate.getNamespace();
+    }
+
+    @Override
+    public int getColumnNumber() {
+        return mDelegate.getColumnNumber();
+    }
+
+    // -- We don't care much about the methods that follow.
+
+    @Override
+    public void require(int i, String s, String s1) throws XmlPullParserException, IOException {
+        throw new UnsupportedOperationException("Only few parser methods are supported.");
+    }
+
+    @Override
+    public boolean getFeature(String s) {
+        throw new UnsupportedOperationException("Only few parser methods are supported.");
+    }
+
+    @Override
+    public void defineEntityReplacementText(String s, String s1) throws XmlPullParserException {
+        throw new UnsupportedOperationException("Only few parser methods are supported.");
+    }
+
+    @Override
+    public Object getProperty(String s) {
+        throw new UnsupportedOperationException("Only few parser methods are supported.");
+    }
+
+    @Override
+    public int nextToken() throws XmlPullParserException, IOException {
+        throw new UnsupportedOperationException("Only few parser methods are supported.");
+    }
+
+    @Override
+    public int getNamespaceCount(int i) throws XmlPullParserException {
+        throw new UnsupportedOperationException("Only few parser methods are supported.");
+    }
+
+    @Override
+    public String getNamespacePrefix(int i) throws XmlPullParserException {
+        throw new UnsupportedOperationException("Only few parser methods are supported.");
+    }
+
+    @Override
+    public String getNamespaceUri(int i) throws XmlPullParserException {
+        throw new UnsupportedOperationException("Only few parser methods are supported.");
+    }
+
+    @Override
+    public boolean isWhitespace() throws XmlPullParserException {
+        throw new UnsupportedOperationException("Only few parser methods are supported.");
+    }
+
+    @Override
+    public char[] getTextCharacters(int[] ints) {
+        throw new UnsupportedOperationException("Only few parser methods are supported.");
+    }
+
+    @Override
+    public String getPrefix() {
+        throw new UnsupportedOperationException("Only few parser methods are supported.");
+    }
+
+    @Override
+    public boolean isEmptyElementTag() throws XmlPullParserException {
+        throw new UnsupportedOperationException("Only few parser methods are supported.");
+    }
+
+    @Override
+    public int getAttributeCount() {
+        throw new UnsupportedOperationException("Only few parser methods are supported.");
+    }
+
+    @Override
+    public String getAttributeNamespace(int i) {
+        throw new UnsupportedOperationException("Only few parser methods are supported.");
+    }
+
+    @Override
+    public String getAttributeName(int i) {
+        throw new UnsupportedOperationException("Only few parser methods are supported.");
+    }
+
+    @Override
+    public String getAttributePrefix(int i) {
+        throw new UnsupportedOperationException("Only few parser methods are supported.");
+    }
+
+    @Override
+    public String getAttributeType(int i) {
+        throw new UnsupportedOperationException("Only few parser methods are supported.");
+    }
+
+    @Override
+    public boolean isAttributeDefault(int i) {
+        throw new UnsupportedOperationException("Only few parser methods are supported.");
+    }
+
+    @Override
+    public String getAttributeValue(int i) {
+        throw new UnsupportedOperationException("Only few parser methods are supported.");
+    }
+
+    @Override
+    public String nextText() throws XmlPullParserException, IOException {
+        throw new UnsupportedOperationException("Only few parser methods are supported.");
+    }
+
+    @Override
+    public int nextTag() throws XmlPullParserException, IOException {
+        throw new UnsupportedOperationException("Only few parser methods are supported.");
+    }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ParserFactory.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ParserFactory.java
index 6e67f59..e273b2c 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ParserFactory.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ParserFactory.java
@@ -53,24 +53,35 @@
     @NonNull
     public static XmlPullParser create(@NonNull File f)
             throws XmlPullParserException, FileNotFoundException {
-        InputStream stream = new FileInputStream(f);
-        return create(stream, f.getName(), f.length());
+        return create(f, false);
     }
 
+    public static XmlPullParser create(@NonNull File f, boolean isLayout)
+      throws XmlPullParserException, FileNotFoundException {
+        InputStream stream = new FileInputStream(f);
+        return create(stream, f.getName(), f.length(), isLayout);
+    }
     @NonNull
     public static XmlPullParser create(@NonNull InputStream stream, @Nullable String name)
         throws XmlPullParserException {
-        return create(stream, name, -1);
+        return create(stream, name, -1, false);
     }
 
     @NonNull
     private static XmlPullParser create(@NonNull InputStream stream, @Nullable String name,
-            long size) throws XmlPullParserException {
+            long size, boolean isLayout) throws XmlPullParserException {
         XmlPullParser parser = instantiateParser(name);
 
         stream = readAndClose(stream, name, size);
 
         parser.setInput(stream, ENCODING);
+        if (isLayout) {
+            try {
+                return new LayoutParserWrapper(parser).peekTillLayoutStart();
+            } catch (IOException e) {
+                throw new XmlPullParserException(null, parser, e);
+            }
+        }
         return parser;
     }
 
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/impl/LayoutParserWrapperTest.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/impl/LayoutParserWrapperTest.java
new file mode 100644
index 0000000..2c33862
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/impl/LayoutParserWrapperTest.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2015 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.layoutlib.bridge.impl;
+
+import org.junit.Test;
+import org.kxml2.io.KXmlParser;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.StringReader;
+
+import static com.android.SdkConstants.NS_RESOURCES;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
+import static org.xmlpull.v1.XmlPullParser.END_TAG;
+import static org.xmlpull.v1.XmlPullParser.START_TAG;
+
+
+public class LayoutParserWrapperTest {
+    @Test
+    @SuppressWarnings("StatementWithEmptyBody")  // some for loops need to be empty statements.
+    public void testDataBindingLayout() throws Exception {
+        LayoutParserWrapper parser = getParserFromString(sDataBindingLayout);
+        parser.peekTillLayoutStart();
+        assertEquals("Expected START_TAG", START_TAG, parser.next());
+        assertEquals("RelativeLayout", parser.getName());
+        for (int next = parser.next(); next != START_TAG && next != END_DOCUMENT;
+             next = parser.next());
+        assertEquals("Expected START_TAG", START_TAG, parser.getEventType());
+        assertEquals("TextView", parser.getName());
+        assertEquals("layout_width incorrect for first text view.", "wrap_content",
+                parser.getAttributeValue(NS_RESOURCES, "layout_width"));
+        // Ensure that data-binding part is stripped.
+        assertEquals("Bound attribute android:text incorrect", "World",
+                parser.getAttributeValue(NS_RESOURCES, "text"));
+        assertEquals("resource attribute 'id' for first text view incorrect.", "@+id/first",
+                parser.getAttributeValue(NS_RESOURCES, "id"));
+        for (int next = parser.next();
+             (next != END_TAG || !"RelativeLayout".equals(parser.getName())) && next != END_DOCUMENT;
+             next = parser.next());
+        assertNotSame("Unexpected end of document", END_DOCUMENT, parser.getEventType());
+        assertEquals("Document didn't end when expected.", END_DOCUMENT, parser.next());
+    }
+
+    @Test
+    @SuppressWarnings("StatementWithEmptyBody")
+    public void testNonDataBindingLayout() throws Exception {
+        LayoutParserWrapper parser = getParserFromString(sNonDataBindingLayout);
+        parser.peekTillLayoutStart();
+        assertEquals("Expected START_TAG", START_TAG, parser.next());
+        assertEquals("RelativeLayout", parser.getName());
+        for (int next = parser.next(); next != START_TAG && next != END_DOCUMENT;
+             next = parser.next());
+        assertEquals("Expected START_TAG", START_TAG, parser.getEventType());
+        assertEquals("TextView", parser.getName());
+        assertEquals("layout_width incorrect for first text view.", "wrap_content",
+                parser.getAttributeValue(NS_RESOURCES, "layout_width"));
+        // Ensure that value isn't modified.
+        assertEquals("Bound attribute android:text incorrect", "@{user.firstName,default=World}",
+                parser.getAttributeValue(NS_RESOURCES, "text"));
+        assertEquals("resource attribute 'id' for first text view incorrect.", "@+id/first",
+                parser.getAttributeValue(NS_RESOURCES, "id"));
+        for (int next = parser.next();
+             (next != END_TAG || !"RelativeLayout".equals(parser.getName())) && next != END_DOCUMENT;
+             next = parser.next());
+        assertNotSame("Unexpected end of document", END_DOCUMENT, parser.getEventType());
+        assertEquals("Document didn't end when expected.", END_DOCUMENT, parser.next());
+    }
+
+    private static LayoutParserWrapper getParserFromString(String layoutContent) throws
+            XmlPullParserException {
+        XmlPullParser parser = new KXmlParser();
+        parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
+        parser.setInput(new StringReader(layoutContent));
+        return new LayoutParserWrapper(parser);
+    }
+
+    private static final String sDataBindingLayout =
+            //language=XML
+            "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+                    "<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+                    "        xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n" +
+                    "        xmlns:tools=\"http://schemas.android.com/tools\"\n" +
+                    "        tools:context=\".MainActivity\"\n" +
+                    "        tools:showIn=\"@layout/activity_main\">\n" +
+                    "\n" +
+                    "    <data>\n" +
+                    "\n" +
+                    "        <variable\n" +
+                    "            name=\"user\"\n" +
+                    "            type=\"com.example.User\" />\n" +
+                    "        <variable\n" +
+                    "            name=\"activity\"\n" +
+                    "            type=\"com.example.MainActivity\" />\n" +
+                    "    </data>\n" +
+                    "\n" +
+                    "    <RelativeLayout\n" +
+                    "        android:layout_width=\"match_parent\"\n" +
+                    "        android:layout_height=\"match_parent\"\n" +
+                    "        android:paddingBottom=\"@dimen/activity_vertical_margin\"\n" +
+                    "        android:paddingLeft=\"@dimen/activity_horizontal_margin\"\n" +
+                    "        android:paddingRight=\"@dimen/activity_horizontal_margin\"\n" +
+                    "        android:paddingTop=\"@dimen/activity_vertical_margin\"\n" +
+                    "        app:layout_behavior=\"@string/appbar_scrolling_view_behavior\"\n" +
+                    "    >\n" +
+                    "\n" +
+                    "        <TextView\n" +
+                    "            android:id=\"@+id/first\"\n" +
+                    "            android:layout_width=\"wrap_content\"\n" +
+                    "            android:layout_alignParentStart=\"true\"\n" +
+                    "            android:layout_alignParentLeft=\"true\"\n" +
+                    "            android:layout_height=\"wrap_content\"\n" +
+                    "            android:text=\"@{user.firstName,default=World}\" />\n" +
+                    "\n" +
+                    "        <TextView\n" +
+                    "            android:id=\"@+id/last\"\n" +
+                    "            android:layout_width=\"wrap_content\"\n" +
+                    "            android:layout_height=\"wrap_content\"\n" +
+                    "            android:layout_toEndOf=\"@id/first\"\n" +
+                    "            android:layout_toRightOf=\"@id/first\"\n" +
+                    "            android:text=\"@{user.lastName,default=Hello}\" />\n" +
+                    "\n" +
+                    "        <Button\n" +
+                    "            android:layout_width=\"wrap_content\"\n" +
+                    "            android:layout_height=\"wrap_content\"\n" +
+                    "            android:layout_below=\"@id/last\"\n" +
+                    "            android:text=\"Submit\"\n" +
+                    "            android:onClick=\"@{activity.onClick}\"/>\n" +
+                    "    </RelativeLayout>\n" +
+                    "</layout>";
+
+    private static final String sNonDataBindingLayout =
+            //language=XML
+            "<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+                    "    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n" +
+                    "    android:layout_width=\"match_parent\"\n" +
+                    "    android:layout_height=\"match_parent\"\n" +
+                    "    android:paddingBottom=\"@dimen/activity_vertical_margin\"\n" +
+                    "    android:paddingLeft=\"@dimen/activity_horizontal_margin\"\n" +
+                    "    android:paddingRight=\"@dimen/activity_horizontal_margin\"\n" +
+                    "    android:paddingTop=\"@dimen/activity_vertical_margin\"\n" +
+                    "    app:layout_behavior=\"@string/appbar_scrolling_view_behavior\"\n" +
+                    ">\n" +
+                    "\n" +
+                    "    <TextView\n" +
+                    "        android:id=\"@+id/first\"\n" +
+                    "        android:layout_width=\"wrap_content\"\n" +
+                    "        android:layout_alignParentStart=\"true\"\n" +
+                    "        android:layout_alignParentLeft=\"true\"\n" +
+                    "        android:layout_height=\"wrap_content\"\n" +
+                    "        android:text=\"@{user.firstName,default=World}\" />\n" +
+                    "\n" +
+                    "    <TextView\n" +
+                    "        android:id=\"@+id/last\"\n" +
+                    "        android:layout_width=\"wrap_content\"\n" +
+                    "        android:layout_height=\"wrap_content\"\n" +
+                    "        android:layout_toEndOf=\"@id/first\"\n" +
+                    "        android:layout_toRightOf=\"@id/first\"\n" +
+                    "        android:text=\"@{user.lastName,default=Hello}\" />\n" +
+                    "\n" +
+                    "    <Button\n" +
+                    "        android:layout_width=\"wrap_content\"\n" +
+                    "        android:layout_height=\"wrap_content\"\n" +
+                    "        android:layout_below=\"@id/last\"\n" +
+                    "        android:text=\"Submit\"\n" +
+                    "        android:onClick=\"@{activity.onClick}\"/>\n" +
+                    "</RelativeLayout>";
+}