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