summaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorGeorge Mount <mount@google.com>2015-03-27 16:05:21 -0700
committerGeorge Mount <mount@google.com>2015-03-27 16:05:21 -0700
commit46bb16c3034c12b347d08273e80adaed30f08104 (patch)
treeddb36c8edd76fb5d08c16575a4b810f03e1da23a /tools
parentefff1c249ea36e3c41e896c07cb00fc1db04672a (diff)
downloadframeworks_base-46bb16c3034c12b347d08273e80adaed30f08104.zip
frameworks_base-46bb16c3034c12b347d08273e80adaed30f08104.tar.gz
frameworks_base-46bb16c3034c12b347d08273e80adaed30f08104.tar.bz2
Make ViewStub support binding variables like include.
Bug 19969378
Diffstat (limited to 'tools')
-rw-r--r--tools/data-binding/compiler/src/main/java/android/databinding/tool/Binding.java64
-rw-r--r--tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/ModelAnalyzer.java10
-rw-r--r--tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/ModelClass.java7
-rw-r--r--tools/data-binding/compiler/src/main/java/android/databinding/tool/store/ResourceBundle.java2
-rw-r--r--tools/data-binding/compiler/src/main/kotlin/android/databinding/tool/writer/LayoutBinderWriter.kt106
-rw-r--r--tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ViewStubBindingAdapterTest.java2
-rw-r--r--tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ViewStubTest.java87
-rw-r--r--tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/layout_with_include.xml1
-rw-r--r--tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/plain_layout.xml17
-rw-r--r--tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/view_stub.xml25
-rw-r--r--tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/view_stub_contents.xml14
-rw-r--r--tools/data-binding/library/src/main/java/android/databinding/ViewStubProxy.java102
12 files changed, 397 insertions, 40 deletions
diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/Binding.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/Binding.java
index 247735d..f2bc96f 100644
--- a/tools/data-binding/compiler/src/main/java/android/databinding/tool/Binding.java
+++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/Binding.java
@@ -20,6 +20,7 @@ import android.databinding.tool.expr.Expr;
import android.databinding.tool.reflection.ModelAnalyzer;
import android.databinding.tool.reflection.ModelClass;
import android.databinding.tool.store.SetterStore;
+import android.databinding.tool.store.SetterStore.SetterCall;
public class Binding {
@@ -37,8 +38,16 @@ public class Binding {
private SetterStore.SetterCall getSetterCall() {
if (mSetterCall == null) {
ModelClass viewType = mTarget.getResolvedType();
- mSetterCall = SetterStore.get(ModelAnalyzer.getInstance()).getSetterCall(mName,
- viewType, mExpr.getResolvedType(), mExpr.getModel().getImports());
+ if (viewType != null && viewType.extendsViewStub()) {
+ if (isViewStubAttribute()) {
+ mSetterCall = new ViewStubDirectCall(mName, viewType, mExpr);
+ } else {
+ mSetterCall = new ViewStubSetterCall(mName);
+ }
+ } else {
+ mSetterCall = SetterStore.get(ModelAnalyzer.getInstance()).getSetterCall(mName,
+ viewType, mExpr.getResolvedType(), mExpr.getModel().getImports());
+ }
}
return mSetterCall;
}
@@ -77,4 +86,55 @@ public class Binding {
public Expr getExpr() {
return mExpr;
}
+
+ private boolean isViewStubAttribute() {
+ if ("android:inflatedId".equals(mName)) {
+ return true;
+ } else if ("android:layout".equals(mName)) {
+ return true;
+ } else if ("android:visibility".equals(mName)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private static class ViewStubSetterCall extends SetterCall {
+ private final String mName;
+
+ public ViewStubSetterCall(String name) {
+ mName = name.substring(name.lastIndexOf(':') + 1);
+ }
+
+ @Override
+ protected String toJavaInternal(String viewExpression, String converted) {
+ return "if (" + viewExpression + ".isInflated()) " + viewExpression +
+ ".getBinding().setVariable(BR." + mName + ", " + converted + ")";
+ }
+
+ @Override
+ public int getMinApi() {
+ return 0;
+ }
+ }
+
+ private static class ViewStubDirectCall extends SetterCall {
+ private final SetterCall mWrappedCall;
+
+ public ViewStubDirectCall(String name, ModelClass viewType, Expr expr) {
+ mWrappedCall = SetterStore.get(ModelAnalyzer.getInstance()).getSetterCall(name,
+ viewType, expr.getResolvedType(), expr.getModel().getImports());
+ }
+
+ @Override
+ protected String toJavaInternal(String viewExpression, String converted) {
+ return "if (!" + viewExpression + ".isInflated()) " +
+ mWrappedCall.toJava(viewExpression + ".getViewStub()", converted);
+ }
+
+ @Override
+ public int getMinApi() {
+ return 0;
+ }
+ }
}
diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/ModelAnalyzer.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/ModelAnalyzer.java
index 318eaa0..187b9dc 100644
--- a/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/ModelAnalyzer.java
+++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/ModelAnalyzer.java
@@ -68,6 +68,8 @@ public abstract class ModelAnalyzer {
public static final String VIEW_DATA_BINDING =
"android.databinding.ViewDataBinding";
+ public static final String VIEW_STUB_CLASS_NAME = "android.view.ViewStub";
+
private ModelClass[] mListTypes;
private ModelClass mMapType;
private ModelClass mStringType;
@@ -77,6 +79,7 @@ public abstract class ModelAnalyzer {
private ModelClass mObservableMapType;
private ModelClass[] mObservableFieldTypes;
private ModelClass mViewBindingType;
+ private ModelClass mViewStubType;
private static ModelAnalyzer sAnalyzer;
@@ -282,6 +285,13 @@ public abstract class ModelAnalyzer {
return mObservableFieldTypes;
}
+ ModelClass getViewStubType() {
+ if (mViewStubType == null) {
+ mViewStubType = findClass(VIEW_STUB_CLASS_NAME, null);
+ }
+ return mViewStubType;
+ }
+
private ModelClass loadClassErasure(String className) {
return findClass(className, null).erasure();
}
diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/ModelClass.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/ModelClass.java
index 5b05a3e..c55a400 100644
--- a/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/ModelClass.java
+++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/ModelClass.java
@@ -129,6 +129,13 @@ public abstract class ModelClass {
}
/**
+ * @return whether or not this ModelClass type extends ViewStub.
+ */
+ public boolean extendsViewStub() {
+ return ModelAnalyzer.getInstance().getViewStubType().isAssignableFrom(this);
+ }
+
+ /**
* @return whether or not this is an Observable type such as ObservableMap, ObservableList,
* or Observable.
*/
diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/store/ResourceBundle.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/store/ResourceBundle.java
index 8468efc..c115387 100644
--- a/tools/data-binding/compiler/src/main/java/android/databinding/tool/store/ResourceBundle.java
+++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/store/ResourceBundle.java
@@ -17,6 +17,8 @@ import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
+import android.databinding.tool.reflection.ModelAnalyzer;
+import android.databinding.tool.reflection.ModelClass;
import android.databinding.tool.util.L;
import android.databinding.tool.util.ParserHelper;
diff --git a/tools/data-binding/compiler/src/main/kotlin/android/databinding/tool/writer/LayoutBinderWriter.kt b/tools/data-binding/compiler/src/main/kotlin/android/databinding/tool/writer/LayoutBinderWriter.kt
index bba5cd9..1ecdf67 100644
--- a/tools/data-binding/compiler/src/main/kotlin/android/databinding/tool/writer/LayoutBinderWriter.kt
+++ b/tools/data-binding/compiler/src/main/kotlin/android/databinding/tool/writer/LayoutBinderWriter.kt
@@ -66,7 +66,7 @@ class ExprModelExt {
}
}
-val ExprModel.ext by Delegates.lazy { (target : ExprModel) ->
+val ExprModel.ext by Delegates.lazy { target : ExprModel ->
ExprModelExt()
}
@@ -74,7 +74,7 @@ fun ExprModel.getUniqueFieldName(base : String) : String = ext.getUniqueFieldNam
fun ExprModel.localizeFlag(set : FlagSet, base : String) : FlagSet = ext.localizeFlag(set, base)
-val BindingTarget.readableUniqueName by Delegates.lazy {(target: BindingTarget) ->
+val BindingTarget.readableUniqueName by Delegates.lazy { target: BindingTarget ->
val variableName : String
if (target.getId() == null) {
variableName = "boundView" + target.getTag()
@@ -84,7 +84,17 @@ val BindingTarget.readableUniqueName by Delegates.lazy {(target: BindingTarget)
target.getModel().ext.getUniqueFieldName(variableName)
}
-val BindingTarget.fieldName by Delegates.lazy { (target : BindingTarget) ->
+fun BindingTarget.superConversion(variable : String) : String {
+ if (isBinder()) {
+ return "${getViewClass()}.bind(${variable})"
+ } else if (getResolvedType() != null && getResolvedType().extendsViewStub()) {
+ return "new android.databinding.ViewStubProxy((android.view.ViewStub) ${variable})"
+ } else {
+ return "(${interfaceType}) ${variable}"
+ }
+}
+
+val BindingTarget.fieldName by Delegates.lazy { target : BindingTarget ->
if (target.getFieldName() == null) {
if (target.getId() == null) {
target.setFieldName("m${target.readableUniqueName.capitalize()}")
@@ -96,61 +106,70 @@ val BindingTarget.fieldName by Delegates.lazy { (target : BindingTarget) ->
target.getFieldName();
}
-val BindingTarget.getterName by Delegates.lazy { (target : BindingTarget) ->
+val BindingTarget.getterName by Delegates.lazy { target : BindingTarget ->
"get${target.readableUniqueName.capitalize()}"
}
-val BindingTarget.androidId by Delegates.lazy { (target : BindingTarget) ->
+val BindingTarget.androidId by Delegates.lazy { target : BindingTarget ->
"R.id.${target.getId().androidId()}"
}
-val Expr.readableUniqueName by Delegates.lazy { (expr : Expr) ->
+val BindingTarget.interfaceType by Delegates.lazy { target : BindingTarget ->
+ if (target.getResolvedType() != null && target.getResolvedType().extendsViewStub()) {
+ "android.databinding.ViewStubProxy"
+ } else {
+ target.getInterfaceType()
+ }
+}
+
+val Expr.readableUniqueName by Delegates.lazy { expr : Expr ->
Log.d { "readableUniqueName for ${expr.getUniqueKey()}" }
val stripped = "${expr.getUniqueKey().stripNonJava()}"
expr.getModel().ext.getUniqueFieldName(stripped)
}
-val Expr.fieldName by Delegates.lazy { (expr : Expr) ->
- "m${expr.readableUniqueName.capitalize()}"
+val Expr.readableName by Delegates.lazy { expr : Expr ->
+ Log.d { "readableUniqueName for ${expr.getUniqueKey()}" }
+ "${expr.getUniqueKey().stripNonJava()}"
+}
+
+val Expr.fieldName by Delegates.lazy { expr : Expr ->
+ "m${expr.readableName.capitalize()}"
}
-val Expr.hasFlag by Delegates.lazy { (expr : Expr) ->
+val Expr.hasFlag by Delegates.lazy { expr : Expr ->
expr.getId() < expr.getModel().getInvalidateableFieldLimit()
}
-val Expr.localName by Delegates.lazy { (expr : Expr) ->
+val Expr.localName by Delegates.lazy { expr : Expr ->
if(expr.isVariable()) expr.fieldName else "${expr.readableUniqueName}"
}
-val Expr.setterName by Delegates.lazy { (expr : Expr) ->
- "set${expr.readableUniqueName.capitalize()}"
+val Expr.setterName by Delegates.lazy { expr : Expr ->
+ "set${expr.readableName.capitalize()}"
}
-val Expr.onChangeName by Delegates.lazy { (expr : Expr) ->
+val Expr.onChangeName by Delegates.lazy { expr : Expr ->
"onChange${expr.readableUniqueName.capitalize()}"
}
-val Expr.getterName by Delegates.lazy { (expr : Expr) ->
- "get${expr.readableUniqueName.capitalize()}"
+val Expr.getterName by Delegates.lazy { expr : Expr ->
+ "get${expr.readableName.capitalize()}"
}
-val Expr.staticFieldName by Delegates.lazy { (expr : Expr) ->
- "s${expr.readableUniqueName.capitalize()}"
-}
-
-val Expr.dirtyFlagName by Delegates.lazy { (expr : Expr) ->
+val Expr.dirtyFlagName by Delegates.lazy { expr : Expr ->
"sFlag${expr.readableUniqueName.capitalize()}"
}
-val Expr.shouldReadFlagName by Delegates.lazy { (expr : Expr) ->
+val Expr.shouldReadFlagName by Delegates.lazy { expr : Expr ->
"sFlagRead${expr.readableUniqueName.capitalize()}"
}
-val Expr.invalidateFlagName by Delegates.lazy { (expr : Expr) ->
+val Expr.invalidateFlagName by Delegates.lazy { expr : Expr ->
"sFlag${expr.readableUniqueName.capitalize()}Invalid"
}
-val Expr.conditionalFlagPrefix by Delegates.lazy { (expr : Expr) ->
+val Expr.conditionalFlagPrefix by Delegates.lazy { expr : Expr ->
"sFlag${expr.readableUniqueName.capitalize()}Is"
}
@@ -238,22 +257,22 @@ fun Expr.isVariable() = this is IdentifierExpr && this.isDynamic()
fun Expr.conditionalFlagName(output : Boolean, suffix : String) = "${dirtyFlagName}_${output}$suffix"
-val Expr.dirtyFlagSet by Delegates.lazy { (expr : Expr) ->
+val Expr.dirtyFlagSet by Delegates.lazy { expr : Expr ->
val fs = FlagSet(expr.getInvalidFlags(), expr.getModel().getFlagBucketCount())
expr.getModel().localizeFlag(fs, expr.dirtyFlagName)
}
-val Expr.invalidateFlagSet by Delegates.lazy { (expr : Expr) ->
+val Expr.invalidateFlagSet by Delegates.lazy { expr : Expr ->
val fs = FlagSet(expr.getId())
expr.getModel().localizeFlag(fs, expr.invalidateFlagName)
}
-val Expr.shouldReadFlagSet by Delegates.lazy { (expr : Expr) ->
+val Expr.shouldReadFlagSet by Delegates.lazy { expr : Expr ->
val fs = FlagSet(expr.getShouldReadFlags(), expr.getModel().getFlagBucketCount())
expr.getModel().localizeFlag(fs, expr.shouldReadFlagName)
}
-val Expr.conditionalFlags by Delegates.lazy { (expr : Expr) ->
+val Expr.conditionalFlags by Delegates.lazy { expr : Expr ->
val model = expr.getModel()
arrayListOf(model.localizeFlag(FlagSet(expr.getRequirementFlagIndex(false)),
"${expr.conditionalFlagPrefix}False"),
@@ -408,18 +427,23 @@ class LayoutBinderWriter(val layoutBinder : LayoutBinder) {
val index = indices.get(it)
if (!it.isUsed()) {
tab(", null")
- } else if (index == null) {
- tab(", (${it.getInterfaceType()}) root")
- } else if (it.isBinder()) {
- tab(", ${it.getViewClass()}.bind(views[${index}])")
- } else {
- tab(", (${it.getInterfaceType()}) views[${index}]")
+ } else{
+ val variableName : String
+ if (index == null) {
+ variableName = "root";
+ } else {
+ variableName = "views[${index}]"
+ }
+ tab(", ${it.superConversion(variableName)}")
}
}
tab(");")
}
val taggedViews = layoutBinder.getBindingTargets().filter{it.isUsed() && !it.isBinder()}
taggedViews.forEach {
+ if (it.getResolvedType() != null && it.getResolvedType().extendsViewStub()) {
+ tab("this.${it.fieldName}.setContainingBinding(this);")
+ }
if (it.getTag() == null) {
if (it.getId() == null) {
tab("this.${it.fieldName} = (${it.getViewClass()}) root;")
@@ -584,7 +608,7 @@ class LayoutBinderWriter(val layoutBinder : LayoutBinder) {
fun declareViews() = kcode("// views") {
layoutBinder.getBindingTargets().filter {it.isUsed() && (it.getId() == null)}.forEach {
- nl("private final ${it.getInterfaceType()} ${it.fieldName};")
+ nl("private final ${it.interfaceType} ${it.fieldName};")
}
}
@@ -596,7 +620,7 @@ class LayoutBinderWriter(val layoutBinder : LayoutBinder) {
fun declareDirtyFlags() = kcode("// dirty flag") {
model.ext.localizedFlags.forEach { flag ->
- flag.notEmpty { (suffix, value) ->
+ flag.notEmpty { suffix, value ->
nl("private")
app(" ", if(flag.isDynamic()) null else "static final");
app(" ", " ${flag.type} ${flag.getLocalName()}$suffix = $value;")
@@ -682,6 +706,14 @@ class LayoutBinderWriter(val layoutBinder : LayoutBinder) {
includedBinders.filter{it.isUsed()}.forEach { binder ->
tab("${binder.fieldName}.executePendingBindings();")
}
+ layoutBinder.getBindingTargets().filter{
+ it.isUsed() && it.getResolvedType() != null && it.getResolvedType().extendsViewStub()
+ }.forEach {
+ tab("if (${it.fieldName}.getBinding() != null) {") {
+ tab("${it.fieldName}.getBinding().executePendingBindings();")
+ }
+ tab("}")
+ }
}
nl("}")
}
@@ -760,12 +792,12 @@ class LayoutBinderWriter(val layoutBinder : LayoutBinder) {
nl("import android.databinding.ViewDataBinding;")
nl("public abstract class ${baseClassName} extends ViewDataBinding {")
layoutBinder.getBindingTargets().filter{it.getId() != null}.forEach {
- tab("public final ${it.getInterfaceType()} ${it.fieldName};")
+ tab("public final ${it.interfaceType} ${it.fieldName};")
}
nl("")
tab("protected ${baseClassName}(android.view.View root_, int localFieldCount") {
layoutBinder.getBindingTargets().filter{it.getId() != null}.forEach {
- tab(", ${it.getInterfaceType()} ${it.readableUniqueName}")
+ tab(", ${it.interfaceType} ${it.readableUniqueName}")
}
}
tab(") {") {
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ViewStubBindingAdapterTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ViewStubBindingAdapterTest.java
index 57bbb5e..2ebe2b0 100644
--- a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ViewStubBindingAdapterTest.java
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ViewStubBindingAdapterTest.java
@@ -33,7 +33,7 @@ public class ViewStubBindingAdapterTest
@Override
protected void setUp() throws Exception {
super.setUp();
- mView = mBinder.view;
+ mView = mBinder.view.getViewStub();
}
public void testLayout() throws Throwable {
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ViewStubTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ViewStubTest.java
new file mode 100644
index 0000000..a63e5df
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ViewStubTest.java
@@ -0,0 +1,87 @@
+/*
+ * 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 android.databinding.testapp;
+
+import android.databinding.testapp.generated.ViewStubBinding;
+import android.databinding.testapp.generated.ViewStubContentsBinding;
+import android.databinding.ViewStubProxy;
+import android.support.v4.util.ArrayMap;
+import android.test.UiThreadTest;
+import android.view.View;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+public class ViewStubTest extends BaseDataBinderTest<ViewStubBinding> {
+
+ public ViewStubTest() {
+ super(ViewStubBinding.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mBinder.setViewStubVisibility(View.GONE);
+ mBinder.setFirstName("Hello");
+ mBinder.setLastName("World");
+ try {
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mBinder.executePendingBindings();
+ }
+ });
+ } catch (Exception e) {
+ throw e;
+ } catch (Throwable t) {
+ throw new Exception(t);
+ }
+ }
+
+ @UiThreadTest
+ public void testInflation() throws Throwable {
+ ViewStubProxy viewStubProxy = mBinder.viewStub;
+ assertFalse(viewStubProxy.isInflated());
+ assertNull(viewStubProxy.getBinding());
+ assertNotNull(viewStubProxy.getViewStub());
+ assertNull(mBinder.getRoot().findViewById(R.id.firstNameContents));
+ assertNull(mBinder.getRoot().findViewById(R.id.lastNameContents));
+ mBinder.setViewStubVisibility(View.VISIBLE);
+ mBinder.executePendingBindings();
+ assertTrue(viewStubProxy.isInflated());
+ assertNotNull(viewStubProxy.getBinding());
+ assertNull(viewStubProxy.getViewStub());
+ ViewStubContentsBinding contentsBinding = (ViewStubContentsBinding)
+ viewStubProxy.getBinding();
+ assertNotNull(contentsBinding.firstNameContents);
+ assertNotNull(contentsBinding.lastNameContents);
+ assertEquals("Hello", contentsBinding.firstNameContents.getText().toString());
+ assertEquals("World", contentsBinding.lastNameContents.getText().toString());
+ }
+
+ @UiThreadTest
+ public void testChangeValues() throws Throwable {
+ ViewStubProxy viewStubProxy = mBinder.viewStub;
+ mBinder.setViewStubVisibility(View.VISIBLE);
+ mBinder.executePendingBindings();
+ ViewStubContentsBinding contentsBinding = (ViewStubContentsBinding)
+ viewStubProxy.getBinding();
+ assertEquals("Hello", contentsBinding.firstNameContents.getText().toString());
+ mBinder.setFirstName("Goodbye");
+ mBinder.executePendingBindings();
+ assertEquals("Goodbye", contentsBinding.firstNameContents.getText().toString());
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/layout_with_include.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/layout_with_include.xml
index 5f5da5a..6504e4c 100644
--- a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/layout_with_include.xml
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/layout_with_include.xml
@@ -26,4 +26,5 @@
bind:innerObject="@{outerObject}"
bind:innerValue="@{`modified ` + outerObject.intValue}"
/>
+ <include layout="@layout/plain_layout" android:id="@+id/plainLayout"/>
</LinearLayout>
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/plain_layout.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/plain_layout.xml
new file mode 100644
index 0000000..2132370
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/plain_layout.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/view_stub.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/view_stub.xml
new file mode 100644
index 0000000..af0796e
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/view_stub.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ xmlns:bind="http://schemas.android.com/apk/res-auto">
+ <variable name="viewStubVisibility" type="int"/>
+ <variable name="firstName" type="String"/>
+ <variable name="lastName" type="String"/>
+ <TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="@{firstName}"
+ android:id="@+id/firstName"
+ />
+ <TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="@{lastName}"
+ android:id="@+id/lastName"
+ />
+
+ <ViewStub android:layout_width="match_parent" android:layout_height="match_parent"
+ android:id="@+id/viewStub"
+ android:visibility="@{viewStubVisibility}"
+ android:layout="@layout/view_stub_contents"
+ bind:firstName="@{firstName}"
+ bind:lastName="@{lastName}"/>
+</LinearLayout>
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/view_stub_contents.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/view_stub_contents.xml
new file mode 100644
index 0000000..9305189
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/view_stub_contents.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <variable name="firstName" type="String"/>
+ <variable name="lastName" type="String"/>
+ <TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="@{firstName}"
+ android:id="@+id/firstNameContents"/>
+ <TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="@{lastName}"
+ android:id="@+id/lastNameContents"/>
+</LinearLayout>
diff --git a/tools/data-binding/library/src/main/java/android/databinding/ViewStubProxy.java b/tools/data-binding/library/src/main/java/android/databinding/ViewStubProxy.java
new file mode 100644
index 0000000..01b878a
--- /dev/null
+++ b/tools/data-binding/library/src/main/java/android/databinding/ViewStubProxy.java
@@ -0,0 +1,102 @@
+/*
+ * 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 android.databinding;
+
+import android.view.View;
+import android.view.ViewStub;
+import android.view.ViewStub.OnInflateListener;
+
+/**
+ * This class represents a ViewStub before and after inflation. Before inflation,
+ * the ViewStub is accessible. After inflation, the ViewDataBinding is accessible
+ * if the inflated View has bindings. If not, the root View will be accessible.
+ */
+public class ViewStubProxy {
+ private ViewStub mViewStub;
+ private ViewDataBinding mViewDataBinding;
+ private View mRoot;
+ private OnInflateListener mOnInflateListener;
+ private ViewDataBinding mContainingBinding;
+
+ private OnInflateListener mProxyListener = new OnInflateListener() {
+ @Override
+ public void onInflate(ViewStub stub, View inflated) {
+ mRoot = inflated;
+ mViewDataBinding = DataBindingUtil.bindTo(inflated, stub.getLayoutResource());
+ mViewStub = null;
+
+ if (mOnInflateListener != null) {
+ mOnInflateListener.onInflate(stub, inflated);
+ mOnInflateListener = null;
+ }
+ mContainingBinding.invalidateAll();
+ mContainingBinding.executePendingBindings();
+ }
+ };
+
+ public ViewStubProxy(ViewStub viewStub) {
+ mViewStub = viewStub;
+ mViewStub.setOnInflateListener(mProxyListener);
+ }
+
+ public void setContainingBinding(ViewDataBinding containingBinding) {
+ mContainingBinding = containingBinding;
+ }
+
+ /**
+ * @return <code>true</code> if the ViewStub has replaced itself with the inflated layout
+ * or <code>false</code> if not.
+ */
+ public boolean isInflated() {
+ return mRoot != null;
+ }
+
+ /**
+ * @return The root View of the layout replacing the ViewStub once it has been inflated.
+ * <code>null</code> is returned prior to inflation.
+ */
+ public View getRoot() {
+ return mRoot;
+ }
+
+ /**
+ * @return The data binding associated with the inflated layout once it has been inflated.
+ * <code>null</code> prior to inflation or if there is no binding associated with the layout.
+ */
+ public ViewDataBinding getBinding() {
+ return mViewDataBinding;
+ }
+
+ /**
+ * @return The ViewStub in the layout or <code>null</code> if the ViewStub has been inflated.
+ */
+ public ViewStub getViewStub() {
+ return mViewStub;
+ }
+
+ /**
+ * Sets the {@link OnInflateListener} to be called when the ViewStub inflates. The proxy must
+ * have an OnInflateListener, so <code>listener</code> will be called immediately after
+ * the proxy's listener is called.
+ *
+ * @param listener The OnInflateListener to notify of successful inflation
+ */
+ public void setOnInflateListener(OnInflateListener listener) {
+ if (mViewStub != null) {
+ mOnInflateListener = listener;
+ }
+ }
+}