First version of StateProvider extension

Bug: 32342385
Test: Unit tests in the CL.
Change-Id: I3534ab77ea37acda1e6ec277275dd7497a843be3
diff --git a/flatfoot-common/init.gradle b/flatfoot-common/init.gradle
index b73c90b..baee4f43 100644
--- a/flatfoot-common/init.gradle
+++ b/flatfoot-common/init.gradle
@@ -37,6 +37,7 @@
 ext.compile_sdk_version = 24
 ext.build_tools_version = "24.0.0"
 ext.intellij_annotation = "12.0"
+ext.release_version = "1.0-SNAPSHOT"
 ext.enablePublicRepos = System.getenv("ALLOW_PUBLIC_REPOS")
 
 def addRepos(RepositoryHandler handler) {
@@ -53,15 +54,15 @@
 
 def createKotlinCheckstyle(Project project) {
     def kotlinCheckstyle = project.tasks.create(name : 'checkstyleKotlin', type: Checkstyle) {
-        configFile rootProject.file('../flatfoot-common/kotlin-checkstyle.xml')
+        configFile file("$lifecyclesRootFolder/../flatfoot-common/kotlin-checkstyle.xml")
         source project.sourceSets.main.allJava
         source project.sourceSets.test.allJava
         ignoreFailures false
         showViolations true
         include '**/*.kt'
         classpath = project.files()
-        checkstyleClasspath = files(rootProject.file('../../../development/tools/checkstyle/checkstyle.jar').path)
-    }
+        checkstyleClasspath = files(file("$lifecyclesRootFolder../../../development/tools/checkstyle/checkstyle.jar").path)
+   }
     project.tasks.findByName("check").dependsOn(kotlinCheckstyle)
     // poor man's line length check
     def lineCheck = project.tasks.create(name : "lineLengthCheck") {
@@ -81,7 +82,7 @@
 
 def createAndroidCheckstyle(Project project) {
     def androidCheckstyle = project.tasks.create(name : 'checkstyleAndroid', type: Checkstyle) {
-        configFile rootProject.file('../../../development/tools/checkstyle/android-style.xml')
+        configFile file("$lifecyclesRootFolder/../../../development/tools/checkstyle/android-style.xml")
         if (project.hasProperty('android')) {
             source project.android.sourceSets.main.java.getSrcDirs()
         }
@@ -92,7 +93,7 @@
         showViolations true
         include '**/*.java'
         classpath = project.files()
-        checkstyleClasspath = files(rootProject.file('../../../development/tools/checkstyle/checkstyle.jar').path)
+        checkstyleClasspath = files(file("$lifecyclesRootFolder/../../../development/tools/checkstyle/checkstyle.jar").path)
     }
     project.tasks.findByName("check").dependsOn(androidCheckstyle)
 }
diff --git a/lifecycle/build.gradle b/lifecycle/build.gradle
index 29f07d4..597e698 100644
--- a/lifecycle/build.gradle
+++ b/lifecycle/build.gradle
@@ -16,6 +16,8 @@
             force "com.google.guava:guava-jdk5:17.0"
         }
     }
+    project.group = 'com.android.support.lifecycle'
+    project.version = release_version
     addRepos(project.repositories)
     if (enablePublicRepos) {
         apply plugin: 'com.android.databinding.localizemaven'
diff --git a/lifecycle/extensions/build.gradle b/lifecycle/extensions/build.gradle
index 0fc1e00..7912d8a 100644
--- a/lifecycle/extensions/build.gradle
+++ b/lifecycle/extensions/build.gradle
@@ -1,4 +1,5 @@
 apply plugin: 'com.android.library'
+apply plugin: 'maven'
 
 android {
     compileSdkVersion compile_sdk_version
@@ -26,8 +27,18 @@
 }
 dependencies {
     compile project(":runtime")
+    compile "com.android.support:support-fragment:$support_lib_version"
     testCompile "junit:junit:$junit_version"
     testCompile "org.mockito:mockito-core:$mockito_version"
 }
 
-createAndroidCheckstyle(project)
\ No newline at end of file
+createAndroidCheckstyle(project)
+
+uploadArchives {
+    repositories {
+        mavenDeployer {
+            repository(url : rootProject.ext.localMavenRepo)
+            pom.artifactId = "extensions"
+        }
+    }
+}
diff --git a/lifecycle/extensions/src/main/java/com/android/support/lifecycle/state/HolderFragment.java b/lifecycle/extensions/src/main/java/com/android/support/lifecycle/state/HolderFragment.java
new file mode 100644
index 0000000..e80c963
--- /dev/null
+++ b/lifecycle/extensions/src/main/java/com/android/support/lifecycle/state/HolderFragment.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2016 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.support.lifecycle.state;
+
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+
+/**
+ * @hide
+ */
+public class HolderFragment extends Fragment {
+
+    private SavedStateProvider mSavedStateProvider = new SavedStateProvider();
+    private RetainedStateProvider mRetainedStateProvider = new RetainedStateProvider();
+
+    public HolderFragment() {
+        setRetainInstance(true);
+    }
+
+    @Override
+    public void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mSavedStateProvider.restoreState(savedInstanceState);
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        mSavedStateProvider.saveState(outState);
+    }
+
+    SavedStateProvider getSavedStateProvider() {
+        return mSavedStateProvider;
+    }
+
+    RetainedStateProvider getRetainedStateProvider() {
+        return mRetainedStateProvider;
+    }
+}
diff --git a/lifecycle/extensions/src/main/java/com/android/support/lifecycle/state/IntStateValue.java b/lifecycle/extensions/src/main/java/com/android/support/lifecycle/state/IntStateValue.java
new file mode 100644
index 0000000..472edc1
--- /dev/null
+++ b/lifecycle/extensions/src/main/java/com/android/support/lifecycle/state/IntStateValue.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2016 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.support.lifecycle.state;
+
+import android.os.Bundle;
+
+/**
+ * Simple wrapper class that holds int value.
+ * This class can be obtained from either {@link SavedStateProvider}
+ * either {@link RetainedStateProvider}
+ */
+public class IntStateValue extends Saveable {
+
+    private int mValue;
+
+    IntStateValue(int i) {
+        mValue = i;
+    }
+
+    /**
+     * Sets the value.
+     *
+     * @param i The new value
+     */
+    public void set(int i) {
+        mValue = i;
+    }
+
+    /**
+     * @return the current value
+     */
+    public int get() {
+        return mValue;
+    }
+
+    @Override
+    void saveTo(Bundle savedState, String key) {
+        savedState.putInt(key, mValue);
+    }
+}
diff --git a/lifecycle/extensions/src/main/java/com/android/support/lifecycle/state/RetainedStateProvider.java b/lifecycle/extensions/src/main/java/com/android/support/lifecycle/state/RetainedStateProvider.java
new file mode 100644
index 0000000..55ac6f0
--- /dev/null
+++ b/lifecycle/extensions/src/main/java/com/android/support/lifecycle/state/RetainedStateProvider.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2016 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.support.lifecycle.state;
+
+import android.support.annotation.MainThread;
+
+import static com.android.support.lifecycle.state.StateMap.typeWarning;
+
+/**
+ * This class facilities work with retained state associated with corresponding
+ * fragment or activity.
+ *
+ * "Retained state" is state which is retained across Activity or Fragment
+ * re-creation (such as from a configuration change).
+ */
+public class RetainedStateProvider {
+
+    private StateMap mStateMap = new StateMap();
+    /**
+     * Returns a {@link StateValue} for the given key. If no value with the given key exists,
+     * a new StateValue will be returned.
+     * If a value with the given key exists but it has a different type,
+     * it will be overridden by a new one.
+     * <p>
+     * Changes, that were made to the returned value after onSaveInstanceState in the corresponding
+     * activity or fragment, may be lost.
+     *
+     * @param key a String, or null
+     * @return {@link StateValue} associated with given key
+     */
+    @MainThread
+    public <T> StateValue<T> stateValue(String key) {
+        Object o = mStateMap.mMap.get(key);
+        StateValue<T> stateValue;
+        try {
+            stateValue = (StateValue<T>) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, StateValue.class.getName(), e);
+            stateValue = null;
+        }
+
+        if (stateValue == null) {
+            stateValue = new StateValue<>();
+            mStateMap.mMap.put(key, stateValue);
+        }
+        return stateValue;
+    }
+
+    /**
+     * Returns a {@link IntStateValue} for the given key. If no value with the given key exists,
+     * a new IntStateValue will be returned.
+     * If a value with the given key exists but it has a different type,
+     * it will be overridden by a new one.
+     * <p>
+     * Resulted {@link IntStateValue} will be initialized with 0.
+     * <p>
+     * Changes, that were made to the returned value after onSaveInstanceState in the corresponding
+     * activity or fragment, may be lost.
+     *
+     * @param key a String, or null
+     * @return {@link IntStateValue} associated with given key
+     */
+    @MainThread
+    public IntStateValue intStateValue(String key) {
+        return mStateMap.intValue(key, 0);
+    }
+
+    /**
+     * Returns a {@link IntStateValue} for the given key. If no value with the given key exists,
+     * a new IntStateValue will be returned.
+     * If a value with the given key exists but it has a different type,
+     * it will be overridden by a new one.
+     * <p>
+     * Resulted {@link IntStateValue} will be initialized with defaultValue.
+     * <p>
+     * Changes, that were made to the returned value after onSaveInstanceState in the corresponding
+     * activity or fragment, may be lost.
+     *
+     * @param key a String, or null
+     * @return {@link IntStateValue} associated with given key
+     */
+    @MainThread
+    public IntStateValue intStateValue(String key, int defaultValue) {
+        return mStateMap.intValue(key, defaultValue);
+    }
+
+}
diff --git a/lifecycle/extensions/src/main/java/com/android/support/lifecycle/state/Saveable.java b/lifecycle/extensions/src/main/java/com/android/support/lifecycle/state/Saveable.java
new file mode 100644
index 0000000..2f04a2d
--- /dev/null
+++ b/lifecycle/extensions/src/main/java/com/android/support/lifecycle/state/Saveable.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2016 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.support.lifecycle.state;
+
+import android.os.Bundle;
+
+abstract class Saveable {
+    abstract void saveTo(Bundle savedState, String key);
+}
diff --git a/lifecycle/extensions/src/main/java/com/android/support/lifecycle/state/SavedStateProvider.java b/lifecycle/extensions/src/main/java/com/android/support/lifecycle/state/SavedStateProvider.java
new file mode 100644
index 0000000..ddfda7e
--- /dev/null
+++ b/lifecycle/extensions/src/main/java/com/android/support/lifecycle/state/SavedStateProvider.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2016 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.support.lifecycle.state;
+
+import static com.android.support.lifecycle.state.StateMap.typeWarning;
+
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.support.annotation.MainThread;
+
+import java.util.Map;
+
+/**
+ * Class that facilitates work with saved state associated with an Activity or Fragment.
+ */
+@SuppressWarnings({"WeakerAccess", "unused"})
+public class SavedStateProvider {
+
+    private static final String TAG = "SavedStateProvider";
+
+    private StateMap mStateMap = new StateMap();
+
+    /**
+     * Returns a {@link SavedStateValue} for the given key. If no value with the given key exists,
+     * a new SavedStateValue will be returned.
+     * If a value with the given key exists but it has a different type,
+     * it will be overridden by a new one.
+     * <p>
+     * Usage of this method is equal to putting value into the Bundle in onSaveInstanceState method.
+     * <p>
+     * Changes, that were made to returned value after onSaveInstanceState in corresponding
+     * activity or fragment, may be lost.
+     *
+     * @param key a String, or null
+     * @return {@link SavedStateValue} associated with given key
+     */
+    @MainThread
+    public <T extends Parcelable> SavedStateValue<T> stateValue(String key) {
+        Object o = mStateMap.mMap.get(key);
+        SavedStateValue<T> stateValue;
+        try {
+            stateValue = (SavedStateValue<T>) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, SavedStateValue.class.getName(), e);
+            stateValue = null;
+        }
+
+        if (stateValue == null) {
+            T value = null;
+            if (mStateMap.mSavedState != null) {
+                value = mStateMap.mSavedState.getParcelable(key);
+            }
+            stateValue = new SavedStateValue<>(value);
+            mStateMap.mMap.put(key, stateValue);
+        }
+        return stateValue;
+    }
+
+    /**
+     * Returns a {@link IntStateValue} for the given key. If no value with the given key exists,
+     * a new IntStateValue will be returned.
+     * If a value with the given key exists but it has a different type,
+     * it will be overridden by a new one.
+     * <p>
+     * Resulted {@link IntStateValue} will be initialized with 0.
+     * <p>
+     * Usage of this method is equal to putting a value into a Bundle in onSaveInstanceState method.
+     * <p>
+     * Changes, that were made to the returned value after onSaveInstanceState in the corresponding
+     * activity or fragment, may be lost.
+     *
+     * @param key a String, or null
+     * @return {@link IntStateValue} associated with given key
+     */
+    @MainThread
+    public IntStateValue intStateValue(String key) {
+        return mStateMap.intValue(key, 0);
+    }
+
+    /**
+     * Returns a {@link IntStateValue} for the given key. If no value with the given key exists,
+     * a new IntStateValue will be returned.
+     * If a value with the given key exists but it has a different type,
+     * it will be overridden by a new one.
+     * <p>
+     * Resulted {@link IntStateValue} will be initialized with defaultValue.
+     * <p>
+     * Usage of this method is equal to putting a value into a Bundle in onSaveInstanceState method.
+     * <p>
+     * Changes, that were made to the returned value after onSaveInstanceState in the corresponding
+     * activity or fragment, may be lost.
+     *
+     * @param key a String, or null
+     * @param defaultValue a value to initialize with if no {@link IntStateValue} was associated
+     *                     with the given key
+     * @return {@link IntStateValue} associated with given key
+     */
+    @MainThread
+    public IntStateValue intStateValue(String key, int defaultValue) {
+        return mStateMap.intValue(key, defaultValue);
+    }
+
+    void restoreState(Bundle savedState) {
+        mStateMap.mSavedState = savedState;
+    }
+
+    void saveState(Bundle outBundle) {
+        if (mStateMap.mSavedState != null) {
+            outBundle.putAll(mStateMap.mSavedState);
+        }
+        Map<String, Object> map = mStateMap.mMap;
+        for (String key: map.keySet()) {
+            ((Saveable) map.get(key)).saveTo(outBundle, key);
+        }
+    }
+}
diff --git a/lifecycle/extensions/src/main/java/com/android/support/lifecycle/state/SavedStateValue.java b/lifecycle/extensions/src/main/java/com/android/support/lifecycle/state/SavedStateValue.java
new file mode 100644
index 0000000..1409440
--- /dev/null
+++ b/lifecycle/extensions/src/main/java/com/android/support/lifecycle/state/SavedStateValue.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2016 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.support.lifecycle.state;
+
+import android.os.Bundle;
+import android.os.Parcelable;
+
+
+/**
+ * Simple wrapper class that holds T value.
+ * This class can be obtained from {@link RetainedStateProvider}
+ * @param <T> - type of the object.
+ */
+public class SavedStateValue<T extends Parcelable> extends Saveable {
+
+    private T mValue;
+
+    SavedStateValue(T mValue) {
+        this.mValue = mValue;
+    }
+
+    /**
+     * Sets the value.
+     *
+     * @param t The new value
+     */
+    public void set(T t) {
+        mValue = t;
+    }
+
+    /**
+     * @return the current value
+     */
+    public T get() {
+        return mValue;
+    }
+
+    @Override
+    void saveTo(Bundle savedState, String key) {
+        savedState.putParcelable(key, mValue);
+    }
+}
diff --git a/lifecycle/extensions/src/main/java/com/android/support/lifecycle/state/StateMap.java b/lifecycle/extensions/src/main/java/com/android/support/lifecycle/state/StateMap.java
new file mode 100644
index 0000000..27a2b2b
--- /dev/null
+++ b/lifecycle/extensions/src/main/java/com/android/support/lifecycle/state/StateMap.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2016 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.support.lifecycle.state;
+
+import android.os.Bundle;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.Map;
+
+class StateMap {
+    private static final String TAG = "StateProvider";
+
+    Map<String, Object> mMap = new HashMap<>();
+    Bundle mSavedState = null;
+
+    IntStateValue intValue(String key, int defaultValue) {
+        IntStateValue intStateValue;
+        Object o = mMap.get(key);
+        try {
+            intStateValue = (IntStateValue) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, IntStateValue.class.getName(), e);
+            intStateValue = null;
+        }
+        if (intStateValue == null) {
+            int value = mSavedState != null ? mSavedState.getInt(key, defaultValue)
+                    : defaultValue;
+            intStateValue = new IntStateValue(value);
+            mMap.put(key, intStateValue);
+        }
+        return intStateValue;
+    }
+
+    static void typeWarning(String key, Object value, String className, ClassCastException e) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("Key ");
+        sb.append(key);
+        sb.append(" expected ");
+        sb.append(className);
+        sb.append(" but value was a ");
+        sb.append(value.getClass().getName());
+        Log.w(TAG, sb.toString());
+        Log.w(TAG, "Attempt to cast generated internal exception:", e);
+    }
+}
diff --git a/lifecycle/extensions/src/main/java/com/android/support/lifecycle/state/StateProviders.java b/lifecycle/extensions/src/main/java/com/android/support/lifecycle/state/StateProviders.java
new file mode 100644
index 0000000..153c962
--- /dev/null
+++ b/lifecycle/extensions/src/main/java/com/android/support/lifecycle/state/StateProviders.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2016 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.support.lifecycle.state;
+
+import android.support.annotation.MainThread;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentManager;
+
+/**
+ *  Factory and utility methods for {@link SavedStateProvider} and {@link RetainedStateProvider}
+ */
+@SuppressWarnings({"WeakerAccess", "unused"})
+public class StateProviders {
+
+    static final String HOLDER_TAG =
+            "com.android.support.lifecycle.state.StateProviderHolderFragment";
+
+    private static HolderFragment holderFragmentFor(FragmentManager manager) {
+        Fragment fragmentByTag = manager.findFragmentByTag(HOLDER_TAG);
+        if (fragmentByTag != null && !(fragmentByTag instanceof HolderFragment)) {
+            throw new IllegalStateException("Unexpected "
+                    + "fragment instance was returned by HOLDER_TAG");
+        }
+
+        HolderFragment holder = (HolderFragment) fragmentByTag;
+        if (holder == null) {
+            holder = new HolderFragment();
+            manager.beginTransaction().add(holder, HOLDER_TAG).commitNowAllowingStateLoss();
+        }
+        return holder;
+    }
+
+    //TODO: create getter from LifecycleProvider
+
+    /**
+     * Returns {@link SavedStateProvider} associated with the given fragment.
+     * if this call was made after fragment saved its state, all later operations on this
+     * {@link SavedStateProvider} may be lost.
+     */
+    @MainThread
+    public static SavedStateProvider savedStateProvider(Fragment fragment) {
+        return holderFragmentFor(fragment.getChildFragmentManager()).getSavedStateProvider();
+    }
+
+    /**
+     * Returns {@link SavedStateProvider} associated with the given activity.
+     * if this call was made after activity saved its state, all later operations on this
+     * {@link SavedStateProvider} may be lost.
+     */
+    @MainThread
+    public static SavedStateProvider savedStateProvider(FragmentActivity activity) {
+        return holderFragmentFor(activity.getSupportFragmentManager()).getSavedStateProvider();
+    }
+
+    /**
+     * Returns {@link RetainedStateProvider} associated with the given fragment.
+     * if this call was made after fragment saved its state, all later operations on this
+     * {@link RetainedStateProvider} may be lost.
+     */
+    @MainThread
+    public static RetainedStateProvider retainedStateProvider(Fragment fragment) {
+        return holderFragmentFor(fragment.getChildFragmentManager()).getRetainedStateProvider();
+    }
+
+    /**
+     * Returns {@link RetainedStateProvider} associated with the given fragment.
+     * if this call was made after fragment saved its state, all later operations on this
+     * {@link RetainedStateProvider} may be lost.
+     */
+    public static RetainedStateProvider retainedStateProvider(FragmentActivity activity) {
+        return holderFragmentFor(activity.getSupportFragmentManager()).getRetainedStateProvider();
+    }
+}
diff --git a/lifecycle/extensions/src/main/java/com/android/support/lifecycle/state/StateValue.java b/lifecycle/extensions/src/main/java/com/android/support/lifecycle/state/StateValue.java
new file mode 100644
index 0000000..2d82560
--- /dev/null
+++ b/lifecycle/extensions/src/main/java/com/android/support/lifecycle/state/StateValue.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2016 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.support.lifecycle.state;
+
+/**
+ * Simple wrapper class that holds T value.
+ * This class can be obtained from {@link RetainedStateProvider}
+ * @param <T> - type of the object.
+ */
+public class StateValue<T> {
+
+    private T mValue;
+
+    StateValue() {
+    }
+
+    /**
+     * Sets the value.
+     *
+     * @param t The new value
+     */
+    public void set(T t) {
+        mValue = t;
+    }
+
+    /**
+     * @return the current value
+     */
+    public T get() {
+        return mValue;
+    }
+}
diff --git a/lifecycle/integration-tests/TestApp/app/.gitignore b/lifecycle/integration-tests/TestApp/app/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/lifecycle/integration-tests/TestApp/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/lifecycle/integration-tests/TestApp/app/build.gradle b/lifecycle/integration-tests/TestApp/app/build.gradle
new file mode 100644
index 0000000..0dd41f7
--- /dev/null
+++ b/lifecycle/integration-tests/TestApp/app/build.gradle
@@ -0,0 +1,45 @@
+apply plugin: 'com.android.application'
+
+android {
+    compileSdkVersion compile_sdk_version
+    buildToolsVersion build_tools_version
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+    }
+
+    defaultConfig {
+        applicationId "com.android.support.lifecycle.testapp"
+        minSdkVersion min_sdk_version
+        targetSdkVersion target_sdk_version
+        versionCode 1
+        versionName "1.0"
+
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+        jackOptions {
+            enabled true
+        }
+    }
+
+    packagingOptions {
+        exclude 'META-INF/services/javax.annotation.processing.Processor'
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+}
+
+createAndroidCheckstyle(project)
+
+dependencies {
+    compile fileTree(dir: 'libs', include: ['*.jar'])
+    compile "com.android.support.lifecycle:extensions:$release_version"
+    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
+        exclude group: 'com.android.support', module: 'support-annotations'
+    })
+    testCompile "junit:junit:$junit_version"
+}
\ No newline at end of file
diff --git a/lifecycle/integration-tests/TestApp/app/proguard-rules.pro b/lifecycle/integration-tests/TestApp/app/proguard-rules.pro
new file mode 100644
index 0000000..575c04e
--- /dev/null
+++ b/lifecycle/integration-tests/TestApp/app/proguard-rules.pro
@@ -0,0 +1,25 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /Users/sergeyv/Library/Android/sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/lifecycle/integration-tests/TestApp/app/src/androidTest/java/com/android/support/lifecycle/state/BaseStateProviderTest.java b/lifecycle/integration-tests/TestApp/app/src/androidTest/java/com/android/support/lifecycle/state/BaseStateProviderTest.java
new file mode 100644
index 0000000..6208587
--- /dev/null
+++ b/lifecycle/integration-tests/TestApp/app/src/androidTest/java/com/android/support/lifecycle/state/BaseStateProviderTest.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2016 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.support.lifecycle.state;
+
+import static com.android.support.lifecycle.state.TestUtils.recreateActivity;
+
+import android.support.test.rule.ActivityTestRule;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+
+import com.android.support.lifecycle.testapp.MainActivity;
+import com.android.support.lifecycle.testapp.R;
+
+import org.junit.Rule;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+
+public abstract class BaseStateProviderTest<T> {
+
+    public enum TestVariant {
+        ACTIVITY,
+        FRAGMENT
+    }
+
+    @Parameterized.Parameters
+    public static Iterable<Object[]> data() {
+        return Arrays.asList(new Object[][]{
+                {TestVariant.ACTIVITY},
+                {TestVariant.FRAGMENT}
+        });
+    }
+
+    @Rule
+    public ActivityTestRule<MainActivity> activityTestRule =
+            new ActivityTestRule<>(MainActivity.class);
+
+    @Parameterized.Parameter
+    public TestVariant mTestVariant;
+
+    private boolean mForceRecreation;
+
+    public BaseStateProviderTest(boolean mForceRecreation) {
+        this.mForceRecreation = mForceRecreation;
+    }
+
+    private MainActivity getActivity() {
+        return activityTestRule.getActivity();
+    }
+
+    protected abstract T getStateProvider(MainActivity activity);
+
+    private void stopRetainingInstances(MainActivity activity) {
+        FragmentManager fragmentManager;
+        if (mTestVariant == TestVariant.FRAGMENT) {
+            Fragment fragment = activity.getSupportFragmentManager()
+                    .findFragmentById(R.id.main_fragment);
+            fragmentManager = fragment.getChildFragmentManager();
+        } else {
+            fragmentManager = activity.getSupportFragmentManager();
+        }
+        TestUtils.stopRetainingInstanceIn(fragmentManager);
+    }
+
+    protected void testRecreation(Action<T>... actions) throws Throwable {
+        MainActivity currentActivity = getActivity();
+        for (int i = 0; i < actions.length; i++) {
+            final Action<T> action = actions[i];
+            final MainActivity activity = currentActivity;
+            activityTestRule.runOnUiThread(() -> {
+                action.run(getStateProvider(activity));
+                if (mForceRecreation) {
+                    stopRetainingInstances(activity);
+                }
+            });
+
+            if (i != actions.length - 1) {
+                currentActivity = recreateActivity(currentActivity, activityTestRule);
+            }
+        }
+    }
+
+    protected interface Action<X> {
+        void run(X provider);
+    }
+}
diff --git a/lifecycle/integration-tests/TestApp/app/src/androidTest/java/com/android/support/lifecycle/state/FragmentStatesTests.java b/lifecycle/integration-tests/TestApp/app/src/androidTest/java/com/android/support/lifecycle/state/FragmentStatesTests.java
new file mode 100644
index 0000000..8c75107
--- /dev/null
+++ b/lifecycle/integration-tests/TestApp/app/src/androidTest/java/com/android/support/lifecycle/state/FragmentStatesTests.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2016 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.support.lifecycle.state;
+
+import static com.android.support.lifecycle.state.StateProviders.retainedStateProvider;
+import static com.android.support.lifecycle.state.StateProviders.savedStateProvider;
+import static com.android.support.lifecycle.state.TestUtils.recreateActivity;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+
+import com.android.support.lifecycle.testapp.MainActivity;
+import com.android.support.lifecycle.testapp.R;
+import com.android.support.lifecycle.testapp.UsualFragment;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class FragmentStatesTests {
+
+    @Rule
+    public ActivityTestRule<MainActivity> activityTestRule =
+            new ActivityTestRule<>(MainActivity.class);
+
+    @Test
+    public void testFragmentSavedStateInBackStack() throws Throwable {
+        testRecreationInBackStack(true,
+                fragment -> savedStateProvider(fragment).intStateValue("key", 261),
+                fragment -> {
+                    IntStateValue intValue = savedStateProvider(fragment).intStateValue("key");
+                    assertThat(intValue.get(), is(261));
+                });
+    }
+
+    @Test
+    public void testFragmentRetainedStateInBackStack() throws Throwable {
+        testRecreationInBackStack(false,
+                fragment -> retainedStateProvider(fragment).intStateValue("key", 261),
+                fragment -> {
+                    int value = retainedStateProvider(fragment).intStateValue("key").get();
+                    assertThat(value, is(261));
+                });
+    }
+
+    protected void testRecreationInBackStack(boolean forceRecreation,
+             Action init, Action checkAfterRecreation) throws Throwable {
+        String tag = "fragment_tag";
+        MainActivity activity = activityTestRule.getActivity();
+        activityTestRule.runOnUiThread(() -> {
+            FragmentManager fragmentManager = activity.getSupportFragmentManager();
+            UsualFragment fragment = new UsualFragment();
+            fragmentManager.beginTransaction().add(R.id.root, fragment, tag).commitNow();
+            init.run(fragment);
+            if (forceRecreation) {
+                TestUtils.stopRetainingInstanceIn(fragment.getChildFragmentManager());
+            }
+
+            Fragment newFragment = new UsualFragment();
+            fragmentManager.beginTransaction()
+                    .replace(R.id.root, newFragment)
+                    .addToBackStack(null)
+                    .commit();
+            fragmentManager.executePendingTransactions();
+        });
+
+        final MainActivity newActivity = recreateActivity(activity, activityTestRule);
+        activityTestRule.runOnUiThread(() -> {
+            FragmentManager fragmentManager = newActivity.getSupportFragmentManager();
+            boolean popped = fragmentManager.popBackStackImmediate();
+            assertThat(popped, is(true));
+            checkAfterRecreation.run(fragmentManager.findFragmentByTag(tag));
+        });
+    }
+
+    interface Action {
+        void run(Fragment fragment);
+    }
+
+}
diff --git a/lifecycle/integration-tests/TestApp/app/src/androidTest/java/com/android/support/lifecycle/state/RetainedStateProviderTest.java b/lifecycle/integration-tests/TestApp/app/src/androidTest/java/com/android/support/lifecycle/state/RetainedStateProviderTest.java
new file mode 100644
index 0000000..0ef611e
--- /dev/null
+++ b/lifecycle/integration-tests/TestApp/app/src/androidTest/java/com/android/support/lifecycle/state/RetainedStateProviderTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2016 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.support.lifecycle.state;
+
+import android.support.v4.app.Fragment;
+
+import com.android.support.lifecycle.testapp.MainActivity;
+import com.android.support.lifecycle.testapp.R;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RetainedStateProviderTest extends BaseStateProviderTest<RetainedStateProvider> {
+
+    public RetainedStateProviderTest() {
+        super(false);
+    }
+
+    @Test
+    public void testIntState() throws Throwable {
+        testRecreation(
+                provider -> provider.intStateValue("int", 261),
+                provider -> assertThat(provider.intStateValue("int", 239).get(), is(261))
+        );
+    }
+
+    @Test
+    public void testStateValue() throws Throwable {
+        final Object o = new Object();
+        testRecreation(
+                provider -> provider.stateValue("object").set(o),
+                provider -> assertThat(provider.stateValue("object").get(), is(o))
+        );
+    }
+
+    @Override
+    protected RetainedStateProvider getStateProvider(MainActivity activity) {
+        if (mTestVariant == TestVariant.ACTIVITY) {
+            return StateProviders.retainedStateProvider(activity);
+        }
+        Fragment fragment = activity.getSupportFragmentManager()
+                .findFragmentById(R.id.main_fragment);
+        return StateProviders.retainedStateProvider(fragment);
+    }
+}
diff --git a/lifecycle/integration-tests/TestApp/app/src/androidTest/java/com/android/support/lifecycle/state/SavedStateProviderTest.java b/lifecycle/integration-tests/TestApp/app/src/androidTest/java/com/android/support/lifecycle/state/SavedStateProviderTest.java
new file mode 100644
index 0000000..708c9eb
--- /dev/null
+++ b/lifecycle/integration-tests/TestApp/app/src/androidTest/java/com/android/support/lifecycle/state/SavedStateProviderTest.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2016 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.support.lifecycle.state;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+import android.graphics.Point;
+import android.support.v4.app.Fragment;
+
+import com.android.support.lifecycle.testapp.MainActivity;
+import com.android.support.lifecycle.testapp.R;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class SavedStateProviderTest extends BaseStateProviderTest<SavedStateProvider> {
+
+    public SavedStateProviderTest() {
+        super(true);
+    }
+
+    @Test
+    public void testIntSavedState() throws Throwable {
+        testRecreation(
+                provider -> provider.intStateValue("xuint", 261),
+                provider -> assertThat(provider.intStateValue("xuint", 239).get(), is(261))
+        );
+    }
+
+    @Test
+    public void testEmptyRecreation() throws Throwable {
+        testRecreation(
+                provider -> provider.intStateValue("xuint", 261),
+                provider -> { },
+                provider -> assertThat(provider.intStateValue("xuint", 239).get(), is(261))
+        );
+    }
+
+    @Test
+    public void testParcelableSavedState() throws Throwable {
+        testRecreation(
+                provider -> provider.<Point>stateValue("object").set(new Point(261, 141)),
+                provider -> assertThat(provider.stateValue("object").get(),
+                        is(new Point(261, 141)))
+        );
+    }
+
+    @Test
+    public void testSameKey() throws Throwable {
+        String key = "shared_key";
+        testRecreation(
+                provider -> {
+                    provider.<Point>stateValue(key).set(new Point(261, 141));
+                    provider.intStateValue(key, 10);
+                },
+                provider -> {
+                    assertThat(provider.intStateValue(key).get(), is(10));
+                    assertThat(provider.<Point>stateValue(key).get(), is((Point) null));
+                }
+        );
+    }
+
+    @Override
+    protected SavedStateProvider getStateProvider(MainActivity activity) {
+        if (mTestVariant == TestVariant.ACTIVITY) {
+            return StateProviders.savedStateProvider(activity);
+        }
+        Fragment fragment = activity.getSupportFragmentManager()
+                .findFragmentById(R.id.main_fragment);
+        return StateProviders.savedStateProvider(fragment);
+    }
+}
diff --git a/lifecycle/integration-tests/TestApp/app/src/androidTest/java/com/android/support/lifecycle/state/TestUtils.java b/lifecycle/integration-tests/TestApp/app/src/androidTest/java/com/android/support/lifecycle/state/TestUtils.java
new file mode 100644
index 0000000..2c8bb73
--- /dev/null
+++ b/lifecycle/integration-tests/TestApp/app/src/androidTest/java/com/android/support/lifecycle/state/TestUtils.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2016 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.support.lifecycle.state;
+
+import android.app.Instrumentation;
+import android.app.Instrumentation.ActivityMonitor;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.UiThreadTestRule;
+import android.support.v4.app.FragmentManager;
+
+import com.android.support.lifecycle.testapp.MainActivity;
+
+public class TestUtils {
+
+    private static final long TIMEOUT_MS = 2000;
+
+    static MainActivity recreateActivity(final MainActivity activity, UiThreadTestRule rule)
+            throws Throwable {
+        ActivityMonitor monitor = new ActivityMonitor(
+                MainActivity.class.getCanonicalName(), null, false);
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        instrumentation.addMonitor(monitor);
+        rule.runOnUiThread(() -> activity.recreate());
+        MainActivity result = (MainActivity) monitor.waitForActivityWithTimeout(TIMEOUT_MS);
+        if (result == null) {
+            throw new RuntimeException("Timeout. Failed to recreate an activity");
+        }
+        return result;
+    }
+
+    static void stopRetainingInstanceIn(FragmentManager fragmentManager) {
+        fragmentManager.findFragmentByTag(StateProviders.HOLDER_TAG).setRetainInstance(false);
+    }
+
+}
diff --git a/lifecycle/integration-tests/TestApp/app/src/main/AndroidManifest.xml b/lifecycle/integration-tests/TestApp/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..80f06fb
--- /dev/null
+++ b/lifecycle/integration-tests/TestApp/app/src/main/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2016 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.
+  -->
+<manifest package="com.android.support.lifecycle.testapp"
+          xmlns:android="http://schemas.android.com/apk/res/android">
+    <application
+        android:allowBackup="true"
+        android:label="Test App"
+        android:supportsRtl="true">
+        <activity android:name="com.android.support.lifecycle.testapp.MainActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
\ No newline at end of file
diff --git a/lifecycle/integration-tests/TestApp/app/src/main/java/com/android/support/lifecycle/testapp/MainActivity.java b/lifecycle/integration-tests/TestApp/app/src/main/java/com/android/support/lifecycle/testapp/MainActivity.java
new file mode 100644
index 0000000..879ba52
--- /dev/null
+++ b/lifecycle/integration-tests/TestApp/app/src/main/java/com/android/support/lifecycle/testapp/MainActivity.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2016 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.support.lifecycle.testapp;
+
+import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
+
+/**
+ * Simple test activity
+ */
+public class MainActivity extends FragmentActivity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity);
+    }
+}
diff --git a/lifecycle/integration-tests/TestApp/app/src/main/java/com/android/support/lifecycle/testapp/UsualFragment.java b/lifecycle/integration-tests/TestApp/app/src/main/java/com/android/support/lifecycle/testapp/UsualFragment.java
new file mode 100644
index 0000000..7a2e326
--- /dev/null
+++ b/lifecycle/integration-tests/TestApp/app/src/main/java/com/android/support/lifecycle/testapp/UsualFragment.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2016 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.support.lifecycle.testapp;
+
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Simple fragment which does nothing.
+ */
+public class UsualFragment extends Fragment {
+
+    @Nullable
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        return new View(getContext());
+    }
+}
diff --git a/lifecycle/integration-tests/TestApp/app/src/main/res/layout/activity.xml b/lifecycle/integration-tests/TestApp/app/src/main/res/layout/activity.xml
new file mode 100644
index 0000000..b46bd5e
--- /dev/null
+++ b/lifecycle/integration-tests/TestApp/app/src/main/res/layout/activity.xml
@@ -0,0 +1,32 @@
+<?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"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <FrameLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:id="@+id/root">
+        <fragment
+            android:id="@+id/main_fragment"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:tag="fragment_tag"
+            android:name="com.android.support.lifecycle.testapp.UsualFragment"
+            tools:context="com.android.support.lifecycle.testapp.MainActivity">
+        </fragment>
+    </FrameLayout>
+</FrameLayout>
\ No newline at end of file
diff --git a/lifecycle/integration-tests/TestApp/build.gradle b/lifecycle/integration-tests/TestApp/build.gradle
new file mode 100644
index 0000000..be8e229
--- /dev/null
+++ b/lifecycle/integration-tests/TestApp/build.gradle
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+buildscript {
+    ext.lifecyclesRootFolder = new File(project.projectDir, "../..")
+    apply from: "${project.projectDir}/../../../flatfoot-common/init.gradle"
+    ext.addRepos(repositories)
+    dependencies {
+        classpath "com.android.tools.build:gradle:$android_gradle_plugin_version"
+        if (enablePublicRepos) {
+            classpath "com.android.databinding:localizemaven:${localize_maven_version}"
+        }
+    }
+}
+
+subprojects { project ->
+    addRepos(project.repositories)
+    project.repositories.maven {
+        url "${localMavenRepo}"
+    }
+}
\ No newline at end of file
diff --git a/lifecycle/integration-tests/TestApp/gradle/wrapper/gradle-wrapper.jar b/lifecycle/integration-tests/TestApp/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..13372ae
--- /dev/null
+++ b/lifecycle/integration-tests/TestApp/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/lifecycle/integration-tests/TestApp/gradle/wrapper/gradle-wrapper.properties b/lifecycle/integration-tests/TestApp/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..467c103
--- /dev/null
+++ b/lifecycle/integration-tests/TestApp/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Mon Dec 28 10:00:20 PST 2015
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-3.2-all.zip
diff --git a/lifecycle/integration-tests/TestApp/gradlew b/lifecycle/integration-tests/TestApp/gradlew
new file mode 100755
index 0000000..9d82f78
--- /dev/null
+++ b/lifecycle/integration-tests/TestApp/gradlew
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "$*"
+}
+
+die ( ) {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+    JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/lifecycle/integration-tests/TestApp/gradlew.bat b/lifecycle/integration-tests/TestApp/gradlew.bat
new file mode 100644
index 0000000..8a0b282
--- /dev/null
+++ b/lifecycle/integration-tests/TestApp/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/lifecycle/integration-tests/TestApp/settings.gradle b/lifecycle/integration-tests/TestApp/settings.gradle
new file mode 100644
index 0000000..9d495b3
--- /dev/null
+++ b/lifecycle/integration-tests/TestApp/settings.gradle
@@ -0,0 +1 @@
+include ':app'
\ No newline at end of file
diff --git a/lifecycle/runtime/build.gradle b/lifecycle/runtime/build.gradle
index 0877cc6..067352f 100644
--- a/lifecycle/runtime/build.gradle
+++ b/lifecycle/runtime/build.gradle
@@ -1,4 +1,5 @@
 apply plugin: 'com.android.library'
+apply plugin: 'maven'
 
 android {
     compileSdkVersion compile_sdk_version
@@ -33,4 +34,13 @@
     testCompile "org.mockito:mockito-core:$mockito_version"
 }
 
-createAndroidCheckstyle(project)
\ No newline at end of file
+createAndroidCheckstyle(project)
+
+uploadArchives {
+    repositories {
+        mavenDeployer {
+            repository(url : rootProject.ext.localMavenRepo)
+            pom.artifactId = "runtime"
+        }
+    }
+}
\ No newline at end of file