Merge "Add PagedList#getConfig" into oc-mr1-support-27.0-dev
diff --git a/compat/src/main/java/android/support/v4/content/res/ResourcesCompat.java b/compat/src/main/java/android/support/v4/content/res/ResourcesCompat.java
index 4c70ce9..15b8ce9 100644
--- a/compat/src/main/java/android/support/v4/content/res/ResourcesCompat.java
+++ b/compat/src/main/java/android/support/v4/content/res/ResourcesCompat.java
@@ -307,11 +307,11 @@
*/
@RestrictTo(LIBRARY_GROUP)
public static Typeface getFont(@NonNull Context context, @FontRes int id, TypedValue value,
- int style) throws NotFoundException {
+ int style, @Nullable FontCallback fontCallback) throws NotFoundException {
if (context.isRestricted()) {
return null;
}
- return loadFont(context, id, value, style, null /* callback */, null /* handler */,
+ return loadFont(context, id, value, style, fontCallback, null /* handler */,
true /* isXmlRequest */);
}
diff --git a/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/ErrorMessages.kt b/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/ErrorMessages.kt
index 10180a6..2f300e7 100644
--- a/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/ErrorMessages.kt
+++ b/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/ErrorMessages.kt
@@ -16,6 +16,9 @@
package android.arch.lifecycle
+import android.arch.lifecycle.model.EventMethod
+import javax.lang.model.element.TypeElement
+
object ErrorMessages {
const val TOO_MANY_ARGS = "callback method cannot have more than 2 parameters"
const val TOO_MANY_ARGS_NOT_ON_ANY = "only callback annotated with ON_ANY " +
@@ -33,4 +36,10 @@
const val INVALID_ENCLOSING_ELEMENT =
"Parent of OnLifecycleEvent should be a class or interface"
const val INVALID_ANNOTATED_ELEMENT = "OnLifecycleEvent can only be added to methods"
+
+ fun failedToGenerateAdapter(type: TypeElement, failureReason: EventMethod) =
+ """
+ Failed to generate an Adapter for $type, because it needs to be able to access to
+ package private method ${failureReason.method.name()} from ${failureReason.type}
+ """.trim()
}
diff --git a/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/input_collector.kt b/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/input_collector.kt
index 43368936..e31b0e9 100644
--- a/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/input_collector.kt
+++ b/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/input_collector.kt
@@ -39,17 +39,22 @@
roundEnv: RoundEnvironment): Map<TypeElement, LifecycleObserverInfo> {
val validator = Validator(processingEnv)
val worldCollector = ObserversCollector(processingEnv)
- roundEnv.getElementsAnnotatedWith(OnLifecycleEvent::class.java).forEach { elem ->
+ val roots = roundEnv.getElementsAnnotatedWith(OnLifecycleEvent::class.java).map { elem ->
if (elem.kind != ElementKind.METHOD) {
validator.printErrorMessage(ErrorMessages.INVALID_ANNOTATED_ELEMENT, elem)
+ null
} else {
val enclosingElement = elem.enclosingElement
if (validator.validateClass(enclosingElement)) {
- worldCollector.collect(MoreElements.asType(enclosingElement))
+ MoreElements.asType(enclosingElement)
+ } else {
+ null
}
}
- }
- return worldCollector.observers
+ }.filterNotNull().toSet()
+ roots.forEach { worldCollector.collect(it) }
+ // filter out everything that arrived from jars
+ return worldCollector.observers.filterKeys { k -> k in roots }
}
class ObserversCollector(processingEnv: ProcessingEnvironment) {
diff --git a/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/transformation.kt b/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/transformation.kt
index 16c3d81..efe1ed7 100644
--- a/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/transformation.kt
+++ b/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/transformation.kt
@@ -78,18 +78,34 @@
return flattened.values.toList()
}
+private fun needsSyntheticAccess(type: TypeElement, eventMethod: EventMethod): Boolean {
+ val executable = eventMethod.method
+ return type.getPackageQName() != eventMethod.packageName()
+ && (executable.isPackagePrivate() || executable.isProtected())
+}
+
fun transformToOutput(processingEnv: ProcessingEnvironment,
world: Map<TypeElement, LifecycleObserverInfo>): List<AdapterClass> {
val flatObservers = flattenObservers(processingEnv, world)
val syntheticMethods = HashMultimap.create<TypeElement, EventMethodCall>()
- flatObservers.filterNot(LifecycleObserverInfo::hasAdapter)
val adapterCalls = flatObservers
- .filterNot(LifecycleObserverInfo::hasAdapter)
+ // filter out everything that arrived from jars
+ .filter { (type) -> type in world }
+ // filter out if it needs synthetic access and we can't generate adapter for it
+ .filterNot { (type, methods) ->
+ methods.any { eventMethod ->
+ val cantGenerate = needsSyntheticAccess(type, eventMethod)
+ && (eventMethod.type !in world)
+ if (cantGenerate) {
+ processingEnv.messager.printMessage(Diagnostic.Kind.WARNING,
+ ErrorMessages.failedToGenerateAdapter(type, eventMethod))
+ }
+ cantGenerate
+ }
+ }
.map { (type, methods) ->
val calls = methods.map { eventMethod ->
- val executable = eventMethod.method
- if (type.getPackageQName() != eventMethod.packageName()
- && (executable.isPackagePrivate() || executable.isProtected())) {
+ if (needsSyntheticAccess(type, eventMethod)) {
EventMethodCall(eventMethod, eventMethod.type)
} else {
EventMethodCall(eventMethod)
@@ -101,9 +117,10 @@
type to calls
}.toMap()
- return adapterCalls.map { (type, calls) ->
- val methods = syntheticMethods.get(type) ?: setOf()
- val synthetic = methods.map { eventMethod -> eventMethod!!.method.method }.toSet()
- AdapterClass(type, calls, synthetic)
- }
+ return adapterCalls
+ .map { (type, calls) ->
+ val methods = syntheticMethods.get(type) ?: emptySet()
+ val synthetic = methods.map { eventMethod -> eventMethod!!.method.method }.toSet()
+ AdapterClass(type, calls, synthetic)
+ }
}
diff --git a/lifecycle/compiler/src/tests/kotlin/android/arch/lifecycle/ValidCasesTest.kt b/lifecycle/compiler/src/tests/kotlin/android/arch/lifecycle/ValidCasesTest.kt
index a4fda54..83d69e0 100644
--- a/lifecycle/compiler/src/tests/kotlin/android/arch/lifecycle/ValidCasesTest.kt
+++ b/lifecycle/compiler/src/tests/kotlin/android/arch/lifecycle/ValidCasesTest.kt
@@ -23,9 +23,10 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
-import javax.tools.StandardLocation
import java.io.File
+import java.lang.Exception
import java.net.URLClassLoader
+import javax.tools.StandardLocation
@RunWith(JUnit4::class)
class ValidCasesTest {
@@ -72,7 +73,7 @@
}
@Test
- fun testInterface1(){
+ fun testInterface1() {
processClass("foo.InterfaceOk1").compilesWithoutError()
}
@@ -118,13 +119,37 @@
@Test
fun testJar() {
- val jarUrl = File("src/tests/test-data/lib/test-library.jar").toURI().toURL()
- val classLoader = URLClassLoader(arrayOf(jarUrl), this.javaClass.classLoader)
JavaSourcesSubject.assertThat(load("foo.DerivedFromJar", ""))
- .withClasspathFrom(classLoader)
+ .withClasspathFrom(libraryClassLoader())
.processedWith(LifecycleProcessor())
.compilesWithoutError().and()
- .generatesSources(load("foo.DerivedFromJar_LifecycleAdapter", "expected")
- )
+ .generatesSources(load("foo.DerivedFromJar_LifecycleAdapter", "expected"))
+ }
+
+ @Test
+ fun testExtendFromJarFailToGenerateAdapter() {
+ val compileTester = JavaSourcesSubject.assertThat(load("foo.DerivedFromJar1", ""))
+ .withClasspathFrom(libraryClassLoader())
+ .processedWith(LifecycleProcessor())
+ .compilesWithoutError()
+ compileTester.withWarningContaining("Failed to generate an Adapter for")
+ doesntGenerateClass(compileTester, "test.library", "ObserverNoAdapter_LifecycleAdapter")
+ doesntGenerateClass(compileTester, "foo", "DerivedFromJar1_LifecycleAdapter")
+ }
+
+ // compile-testing has fancy, but not always convenient API
+ private fun doesntGenerateClass(compile: CompileTester.SuccessfulCompilationClause,
+ packageName: String, className: String) {
+ try {
+ compile.and().generatesFileNamed(StandardLocation.CLASS_OUTPUT,
+ packageName, "$className.class")
+ throw Exception("$packageName.$className shouldn't be generated")
+ } catch (e: AssertionError) {
+ }
+ }
+
+ private fun libraryClassLoader(): URLClassLoader {
+ val jarUrl = File("src/tests/test-data/lib/test-library.jar").toURI().toURL()
+ return URLClassLoader(arrayOf(jarUrl), this.javaClass.classLoader)
}
}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/OnSaveInstanceStateListener.java b/lifecycle/compiler/src/tests/test-data/DerivedFromJar1.java
similarity index 61%
rename from lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/OnSaveInstanceStateListener.java
rename to lifecycle/compiler/src/tests/test-data/DerivedFromJar1.java
index 3acc622..efcfaff 100644
--- a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/OnSaveInstanceStateListener.java
+++ b/lifecycle/compiler/src/tests/test-data/DerivedFromJar1.java
@@ -14,16 +14,15 @@
* limitations under the License.
*/
-package android.arch.lifecycle.testapp;
+package foo;
-/**
- * Observer that can be set on {@link LifecycleObservableActivity} or
- * {@link LifecycleObservableFragment} so that tests can be notified when certain events occur.
- */
-public interface OnSaveInstanceStateListener {
+import static android.arch.lifecycle.Lifecycle.Event.ON_START;
- /**
- * Called in onSaveInstanceState() after super.onSaveInstanceState().
- */
- void onSaveInstanceState();
+import android.arch.lifecycle.Lifecycle.Event;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+public class DerivedFromJar1 extends test.library.PPObserverNoAdapter {
+ @OnLifecycleEvent(ON_START)
+ public void doAnother() {
+ }
}
diff --git a/lifecycle/compiler/src/tests/test-data/expected/DerivedFromJar1_LifecycleAdapter.java b/lifecycle/compiler/src/tests/test-data/expected/DerivedFromJar1_LifecycleAdapter.java
new file mode 100644
index 0000000..2ff2db2
--- /dev/null
+++ b/lifecycle/compiler/src/tests/test-data/expected/DerivedFromJar1_LifecycleAdapter.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 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 foo;
+
+import android.arch.lifecycle.GeneratedAdapter;
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LifecycleOwner;
+import android.arch.lifecycle.MethodCallsLogger;
+import java.lang.Override;
+import javax.annotation.Generated;
+
+@Generated("android.arch.lifecycle.LifecycleProcessor")
+public class DerivedFromJar1_LifecycleAdapter implements GeneratedAdapter {
+ final DerivedFromJar1 mReceiver;
+
+ DerivedFromJar_LifecycleAdapter(DerivedFromJar1 receiver) {
+ this.mReceiver = receiver;
+ }
+
+ @Override
+ public void callMethods(LifecycleOwner owner, Lifecycle.Event event, boolean onAny,
+ MethodCallsLogger logger) {
+ boolean hasLogger = logger != null;
+ if (onAny) {
+ return;
+ }
+ if (event == Lifecycle.Event.ON_STOP) {
+ if (!hasLogger || logger.approveCall("doOnStop", 1)) {
+ mReceiver.doOnStop();
+ }
+ return;
+ }
+ if (event == Lifecycle.Event.ON_START) {
+ if (!hasLogger || logger.approveCall("doAnother", 1)) {
+ mReceiver.doAnother();
+ }
+ return;
+ }
+ }
+}
diff --git a/lifecycle/compiler/src/tests/test-data/expected/ObserverNoAdapter_LifecycleAdapter.java b/lifecycle/compiler/src/tests/test-data/expected/ObserverNoAdapter_LifecycleAdapter.java
new file mode 100644
index 0000000..c2dc1b6
--- /dev/null
+++ b/lifecycle/compiler/src/tests/test-data/expected/ObserverNoAdapter_LifecycleAdapter.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2017 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 test.library;
+
+import android.arch.lifecycle.GeneratedAdapter;
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LifecycleOwner;
+import android.arch.lifecycle.MethodCallsLogger;
+import java.lang.Override;
+import javax.annotation.Generated;
+
+@Generated("android.arch.lifecycle.LifecycleProcessor")
+public class ObserverNoAdapter_LifecycleAdapter implements GeneratedAdapter {
+ final ObserverNoAdapter mReceiver;
+
+ ObserverNoAdapter_LifecycleAdapter(ObserverNoAdapter receiver) {
+ this.mReceiver = receiver;
+ }
+
+ @Override
+ public void callMethods(LifecycleOwner owner, Lifecycle.Event event, boolean onAny,
+ MethodCallsLogger logger) {
+ boolean hasLogger = logger != null;
+ if (onAny) {
+ return;
+ }
+ if (event == Lifecycle.Event.ON_STOP) {
+ if (!hasLogger || logger.approveCall("doOnStop", 1)) {
+ mReceiver.doOnStop();
+ }
+ return;
+ }
+ }
+}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/OnSaveInstanceStateListener.java b/lifecycle/compiler/src/tests/test-data/lib/src/test/library/ObserverNoAdapter.java
similarity index 60%
copy from lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/OnSaveInstanceStateListener.java
copy to lifecycle/compiler/src/tests/test-data/lib/src/test/library/ObserverNoAdapter.java
index 3acc622..ea000f7 100644
--- a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/OnSaveInstanceStateListener.java
+++ b/lifecycle/compiler/src/tests/test-data/lib/src/test/library/ObserverNoAdapter.java
@@ -14,16 +14,17 @@
* limitations under the License.
*/
-package android.arch.lifecycle.testapp;
+package test.library;
-/**
- * Observer that can be set on {@link LifecycleObservableActivity} or
- * {@link LifecycleObservableFragment} so that tests can be notified when certain events occur.
- */
-public interface OnSaveInstanceStateListener {
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
- /**
- * Called in onSaveInstanceState() after super.onSaveInstanceState().
- */
- void onSaveInstanceState();
+import android.arch.lifecycle.Lifecycle.Event;
+import android.arch.lifecycle.LifecycleObserver;
+import android.arch.lifecycle.LifecycleOwner;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+public class ObserverNoAdapter implements LifecycleObserver {
+ @OnLifecycleEvent(ON_STOP)
+ public void doOnStop() {
+ }
}
diff --git a/lifecycle/compiler/src/tests/test-data/lib/src/test/library/PPObserverNoAdapter.java b/lifecycle/compiler/src/tests/test-data/lib/src/test/library/PPObserverNoAdapter.java
new file mode 100644
index 0000000..06e10b5
--- /dev/null
+++ b/lifecycle/compiler/src/tests/test-data/lib/src/test/library/PPObserverNoAdapter.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 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 test.library;
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_START;
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+
+import android.arch.lifecycle.Lifecycle.Event;
+import android.arch.lifecycle.LifecycleObserver;
+import android.arch.lifecycle.LifecycleOwner;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+public class PPObserverNoAdapter implements LifecycleObserver {
+ @OnLifecycleEvent(ON_START)
+ protected void doOnStart() {
+ }
+
+ @OnLifecycleEvent(ON_STOP)
+ protected void doOnStop() {
+ }
+}
diff --git a/lifecycle/compiler/src/tests/test-data/lib/src/test/library/PPObserverWithAdapter.java b/lifecycle/compiler/src/tests/test-data/lib/src/test/library/PPObserverWithAdapter.java
new file mode 100644
index 0000000..25a9b5d
--- /dev/null
+++ b/lifecycle/compiler/src/tests/test-data/lib/src/test/library/PPObserverWithAdapter.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 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 test.library;
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_START;
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+
+import android.arch.lifecycle.Lifecycle.Event;
+import android.arch.lifecycle.LifecycleObserver;
+import android.arch.lifecycle.LifecycleOwner;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+public class PPObserverWithAdapter implements LifecycleObserver {
+ @OnLifecycleEvent(ON_START)
+ protected void doOnStart() {
+ }
+
+ @OnLifecycleEvent(ON_STOP)
+ protected void doOnStop() {
+ }
+}
diff --git a/lifecycle/compiler/src/tests/test-data/lib/test-library.jar b/lifecycle/compiler/src/tests/test-data/lib/test-library.jar
index f1156ad..71af37f 100644
--- a/lifecycle/compiler/src/tests/test-data/lib/test-library.jar
+++ b/lifecycle/compiler/src/tests/test-data/lib/test-library.jar
Binary files differ
diff --git a/lifecycle/extensions/src/test/java/android/arch/lifecycle/LiveDataTest.java b/lifecycle/extensions/src/test/java/android/arch/lifecycle/LiveDataTest.java
index 4c28567..647d5d7 100644
--- a/lifecycle/extensions/src/test/java/android/arch/lifecycle/LiveDataTest.java
+++ b/lifecycle/extensions/src/test/java/android/arch/lifecycle/LiveDataTest.java
@@ -585,30 +585,26 @@
mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
- // Marking state as CREATED should not call onInactive.
+ // Marking state as CREATED should call onInactive.
reset(mActiveObserversChanged);
mRegistry.markState(Lifecycle.State.CREATED);
- verify(mActiveObserversChanged, never()).onCall(anyBoolean());
+ verify(mActiveObserversChanged).onCall(false);
+ reset(mActiveObserversChanged);
// Setting a new value should trigger LiveData to realize the Lifecycle it is observing
// is in a state where the LiveData should be inactive, so the LiveData will call onInactive
// and the Observer shouldn't be affected.
mLiveData.setValue("1");
- verify(mActiveObserversChanged).onCall(false);
- verify(observer, never()).onChanged(anyString());
- // Sanity check. Because we've only marked the state as CREATED, the LifecycleRegistry
- // was internally never moved away from the StartedState, and thus sending the ON_START
- // event again will have no affect on the LiveData.
- reset(mActiveObserversChanged);
- reset(observer);
- mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+ // state is already CREATED so should not call again
verify(mActiveObserversChanged, never()).onCall(anyBoolean());
verify(observer, never()).onChanged(anyString());
- // If the lifecycle moves back to a started/resumed state, the LiveData should again be made
- // active, and therefore the observer should have been called with the new data.
- mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
+ // Sanity check. Because we've only marked the state as CREATED, sending ON_START
+ // should re-dispatch events.
+ reset(mActiveObserversChanged);
+ reset(observer);
+ mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
verify(mActiveObserversChanged).onCall(true);
verify(observer).onChanged("1");
}
@@ -645,19 +641,19 @@
verify(observer1, never()).onChanged(anyString());
verify(observer2).onChanged("1");
- // Now we set the other Lifecycle to be inactive, but the LiveData will still be active
- // until it's value is updated.
+ // Now we set the other Lifecycle to be inactive, live data should become inactive.
reset(observer1);
reset(observer2);
mRegistry2.markState(Lifecycle.State.CREATED);
- verify(mActiveObserversChanged, never()).onCall(anyBoolean());
+ verify(mActiveObserversChanged).onCall(false);
verify(observer1, never()).onChanged(anyString());
verify(observer2, never()).onChanged(anyString());
- // Now we post another value, because both lifecycles are in the Created state, the LiveData
- // will be made inactive, and neither of the observers will be called.
+ // Now we post another value, because both lifecycles are in the Created state, live data
+ // will not dispatch any values
+ reset(mActiveObserversChanged);
mLiveData.setValue("2");
- verify(mActiveObserversChanged).onCall(false);
+ verify(mActiveObserversChanged, never()).onCall(anyBoolean());
verify(observer1, never()).onChanged(anyString());
verify(observer2, never()).onChanged(anyString());
@@ -665,6 +661,7 @@
// be made active and it's associated Observer will be called with the new value, but the
// Observer associated with the Lifecycle that is still in the Created state won't be
// called.
+ reset(mActiveObserversChanged);
mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
verify(mActiveObserversChanged).onCall(true);
verify(observer1).onChanged("2");
diff --git a/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/ActivityFullLifecycleTest.java b/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/ActivityFullLifecycleTest.java
index ee4e661..78dd015 100644
--- a/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/ActivityFullLifecycleTest.java
+++ b/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/ActivityFullLifecycleTest.java
@@ -16,48 +16,43 @@
package android.arch.lifecycle;
-import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE;
-import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY;
-import static android.arch.lifecycle.Lifecycle.Event.ON_PAUSE;
-import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
-import static android.arch.lifecycle.Lifecycle.Event.ON_START;
-import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
-import static android.arch.lifecycle.testapp.TestEvent.ACTIVITY_CALLBACK;
-import static android.arch.lifecycle.testapp.TestEvent.LIFECYCLE_EVENT;
+import static android.arch.lifecycle.TestUtils.OrderedTuples.CREATE;
+import static android.arch.lifecycle.TestUtils.OrderedTuples.DESTROY;
+import static android.arch.lifecycle.TestUtils.OrderedTuples.PAUSE;
+import static android.arch.lifecycle.TestUtils.OrderedTuples.RESUME;
+import static android.arch.lifecycle.TestUtils.OrderedTuples.START;
+import static android.arch.lifecycle.TestUtils.OrderedTuples.STOP;
+import static android.arch.lifecycle.TestUtils.flatMap;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import android.app.Activity;
import android.arch.lifecycle.Lifecycle.Event;
-import android.arch.lifecycle.testapp.CollectingActivity;
+import android.arch.lifecycle.testapp.CollectingLifecycleOwner;
+import android.arch.lifecycle.testapp.CollectingSupportActivity;
import android.arch.lifecycle.testapp.FrameworkLifecycleRegistryActivity;
-import android.arch.lifecycle.testapp.FullLifecycleTestActivity;
-import android.arch.lifecycle.testapp.SupportLifecycleRegistryActivity;
import android.arch.lifecycle.testapp.TestEvent;
import android.support.test.filters.SmallTest;
import android.support.test.rule.ActivityTestRule;
-import android.util.Pair;
+import android.support.v4.util.Pair;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
-import java.util.ArrayList;
import java.util.List;
@SmallTest
@RunWith(Parameterized.class)
public class ActivityFullLifecycleTest {
@Rule
- public ActivityTestRule activityTestRule =
- new ActivityTestRule<>(FullLifecycleTestActivity.class);
+ public final ActivityTestRule<? extends CollectingLifecycleOwner> activityTestRule;
@Parameterized.Parameters
public static Class[] params() {
- return new Class[]{FullLifecycleTestActivity.class,
- SupportLifecycleRegistryActivity.class,
+ return new Class[]{CollectingSupportActivity.class,
FrameworkLifecycleRegistryActivity.class};
}
@@ -68,28 +63,13 @@
@Test
- public void testFullLifecycle() throws InterruptedException {
- Activity activity = activityTestRule.getActivity();
- List<Pair<TestEvent, Event>> results = ((CollectingActivity) activity)
- .waitForCollectedEvents();
+ public void testFullLifecycle() throws Throwable {
+ CollectingLifecycleOwner owner = activityTestRule.getActivity();
+ TestUtils.waitTillResumed(owner, activityTestRule);
+ activityTestRule.finishActivity();
- Event[] expectedEvents =
- new Event[]{ON_CREATE, ON_START, ON_RESUME, ON_PAUSE, ON_STOP, ON_DESTROY};
-
- List<Pair<TestEvent, Event>> expected = new ArrayList<>();
- boolean beforeResume = true;
- for (Event i : expectedEvents) {
- if (beforeResume) {
- expected.add(new Pair<>(ACTIVITY_CALLBACK, i));
- expected.add(new Pair<>(LIFECYCLE_EVENT, i));
- } else {
- expected.add(new Pair<>(LIFECYCLE_EVENT, i));
- expected.add(new Pair<>(ACTIVITY_CALLBACK, i));
- }
- if (i == ON_RESUME) {
- beforeResume = false;
- }
- }
- assertThat(results, is(expected));
+ TestUtils.waitTillDestroyed(owner, activityTestRule);
+ List<Pair<TestEvent, Event>> results = owner.copyCollectedEvents();
+ assertThat(results, is(flatMap(CREATE, START, RESUME, PAUSE, STOP, DESTROY)));
}
}
diff --git a/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/LiveDataOnSaveInstanceStateTest.java b/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/LiveDataOnSaveInstanceStateTest.java
index e1b4a28..836cfff 100644
--- a/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/LiveDataOnSaveInstanceStateTest.java
+++ b/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/LiveDataOnSaveInstanceStateTest.java
@@ -20,10 +20,9 @@
import static org.hamcrest.MatcherAssert.assertThat;
import android.app.Instrumentation;
-import android.arch.lifecycle.testapp.LifecycleObservableActivity;
-import android.arch.lifecycle.testapp.LifecycleObservableFragment;
+import android.arch.lifecycle.testapp.CollectingSupportActivity;
+import android.arch.lifecycle.testapp.CollectingSupportFragment;
import android.arch.lifecycle.testapp.NavigationDialogActivity;
-import android.arch.lifecycle.testapp.OnSaveInstanceStateObservable;
import android.content.Intent;
import android.os.Build;
import android.support.test.InstrumentationRegistry;
@@ -37,84 +36,80 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class LiveDataOnSaveInstanceStateTest {
@Rule
- public ActivityTestRule<LifecycleObservableActivity> mActivityTestRule =
- new ActivityTestRule<>(LifecycleObservableActivity.class);
+ public ActivityTestRule<CollectingSupportActivity> mActivityTestRule =
+ new ActivityTestRule<>(CollectingSupportActivity.class);
@Test
@SdkSuppress(maxSdkVersion = Build.VERSION_CODES.M)
public void liveData_partiallyObscuredActivity_maxSdkM() throws Throwable {
- LifecycleObservableActivity activity = mActivityTestRule.getActivity();
+ CollectingSupportActivity activity = mActivityTestRule.getActivity();
- liveData_partiallyObscuredLifecycleOwner_maxSdkM(activity, activity, activity);
+ liveData_partiallyObscuredLifecycleOwner_maxSdkM(activity);
}
@Test
@SdkSuppress(maxSdkVersion = Build.VERSION_CODES.M)
public void liveData_partiallyObscuredActivityWithFragment_maxSdkM() throws Throwable {
- LifecycleObservableActivity activity = mActivityTestRule.getActivity();
- LifecycleObservableFragment fragment = new LifecycleObservableFragment();
+ CollectingSupportActivity activity = mActivityTestRule.getActivity();
+ CollectingSupportFragment fragment = new CollectingSupportFragment();
mActivityTestRule.runOnUiThread(() -> activity.replaceFragment(fragment));
- liveData_partiallyObscuredLifecycleOwner_maxSdkM(activity, fragment, fragment);
+ liveData_partiallyObscuredLifecycleOwner_maxSdkM(fragment);
}
@Test
@SdkSuppress(maxSdkVersion = Build.VERSION_CODES.M)
public void liveData_partiallyObscuredActivityFragmentInFragment_maxSdkM() throws Throwable {
- LifecycleObservableActivity activity = mActivityTestRule.getActivity();
- LifecycleObservableFragment fragment = new LifecycleObservableFragment();
- LifecycleObservableFragment fragment2 = new LifecycleObservableFragment();
+ CollectingSupportActivity activity = mActivityTestRule.getActivity();
+ CollectingSupportFragment fragment = new CollectingSupportFragment();
+ CollectingSupportFragment fragment2 = new CollectingSupportFragment();
mActivityTestRule.runOnUiThread(() -> {
activity.replaceFragment(fragment);
fragment.replaceFragment(fragment2);
});
- liveData_partiallyObscuredLifecycleOwner_maxSdkM(activity, fragment2, fragment2);
+ liveData_partiallyObscuredLifecycleOwner_maxSdkM(fragment2);
}
@Test
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.N)
public void liveData_partiallyObscuredActivity_minSdkN() throws Throwable {
- LifecycleObservableActivity activity = mActivityTestRule.getActivity();
+ CollectingSupportActivity activity = mActivityTestRule.getActivity();
- liveData_partiallyObscuredLifecycleOwner_minSdkN(activity, activity);
+ liveData_partiallyObscuredLifecycleOwner_minSdkN(activity);
}
@Test
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.N)
public void liveData_partiallyObscuredActivityWithFragment_minSdkN() throws Throwable {
- LifecycleObservableActivity activity = mActivityTestRule.getActivity();
- LifecycleObservableFragment fragment = new LifecycleObservableFragment();
+ CollectingSupportActivity activity = mActivityTestRule.getActivity();
+ CollectingSupportFragment fragment = new CollectingSupportFragment();
mActivityTestRule.runOnUiThread(() -> activity.replaceFragment(fragment));
- liveData_partiallyObscuredLifecycleOwner_minSdkN(activity, fragment);
+ liveData_partiallyObscuredLifecycleOwner_minSdkN(fragment);
}
@Test
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.N)
public void liveData_partiallyObscuredActivityFragmentInFragment_minSdkN() throws Throwable {
- LifecycleObservableActivity activity = mActivityTestRule.getActivity();
- LifecycleObservableFragment fragment = new LifecycleObservableFragment();
- LifecycleObservableFragment fragment2 = new LifecycleObservableFragment();
+ CollectingSupportActivity activity = mActivityTestRule.getActivity();
+ CollectingSupportFragment fragment = new CollectingSupportFragment();
+ CollectingSupportFragment fragment2 = new CollectingSupportFragment();
mActivityTestRule.runOnUiThread(() -> {
activity.replaceFragment(fragment);
fragment.replaceFragment(fragment2);
});
- liveData_partiallyObscuredLifecycleOwner_minSdkN(activity, fragment2);
+ liveData_partiallyObscuredLifecycleOwner_minSdkN(fragment2);
}
- private void liveData_partiallyObscuredLifecycleOwner_maxSdkM(FragmentActivity activity,
- LifecycleOwner lifecycleOwner,
- OnSaveInstanceStateObservable onSaveInstanceStateObservable)
+ private void liveData_partiallyObscuredLifecycleOwner_maxSdkM(LifecycleOwner lifecycleOwner)
throws Throwable {
final AtomicInteger atomicInteger = new AtomicInteger(0);
MutableLiveData<Integer> mutableLiveData = new MutableLiveData<>();
@@ -124,27 +119,9 @@
mutableLiveData.observe(lifecycleOwner, atomicInteger::set);
- Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor(
- NavigationDialogActivity.class.getCanonicalName(), null, false);
- Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
- instrumentation.addMonitor(monitor);
+ final FragmentActivity dialogActivity = launchDialog();
- // Launch the NavigationDialogActivity, partially obscuring the activity, and wait for the
- // the onSaveInstanceStateObservable's onSaveInstanceState method to be called. On API 23
- // and below, this should be the final 'lifecycle' method called.
- CountDownLatch countDownLatch = new CountDownLatch(1);
- onSaveInstanceStateObservable.setOnSaveInstanceStateListener(() -> {
- countDownLatch.countDown();
- onSaveInstanceStateObservable.setOnSaveInstanceStateListener(null);
- });
- Intent dummyIntent = new Intent(InstrumentationRegistry.getTargetContext(),
- NavigationDialogActivity.class);
- activity.startActivity(dummyIntent);
- countDownLatch.await(1, TimeUnit.SECONDS);
-
- // Sanity check.
- assertThat(lifecycleOwner.getLifecycle().getCurrentState(),
- is(Lifecycle.State.CREATED));
+ TestUtils.waitTillCreated(lifecycleOwner, mActivityTestRule);
// Change the LiveData value and assert that the observer is not called given that the
// lifecycle is in the CREATED state.
@@ -153,14 +130,12 @@
// Finish the dialog Activity, wait for the main activity to be resumed, and assert that
// the observer's onChanged method is called.
- FragmentActivity dialogActivity = (FragmentActivity) monitor.waitForActivity();
mActivityTestRule.runOnUiThread(dialogActivity::finish);
TestUtils.waitTillResumed(lifecycleOwner, mActivityTestRule);
assertThat(atomicInteger.get(), is(1));
}
- private void liveData_partiallyObscuredLifecycleOwner_minSdkN(FragmentActivity activity,
- LifecycleOwner lifecycleOwner)
+ private void liveData_partiallyObscuredLifecycleOwner_minSdkN(LifecycleOwner lifecycleOwner)
throws Throwable {
final AtomicInteger atomicInteger = new AtomicInteger(0);
MutableLiveData<Integer> mutableLiveData = new MutableLiveData<>();
@@ -170,27 +145,33 @@
mutableLiveData.observe(lifecycleOwner, atomicInteger::set);
- Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor(
- NavigationDialogActivity.class.getCanonicalName(), null, false);
- Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
- instrumentation.addMonitor(monitor);
-
// Launch the NavigationDialogActivity, partially obscuring the activity, and wait for the
// lifecycleOwner to hit onPause (or enter the STARTED state). On API 24 and above, this
// onPause should be the last lifecycle method called (and the STARTED state should be the
// final resting state).
- Intent dummyIntent = new Intent(InstrumentationRegistry.getTargetContext(),
- NavigationDialogActivity.class);
- activity.startActivity(dummyIntent);
+ launchDialog();
TestUtils.waitTillStarted(lifecycleOwner, mActivityTestRule);
- // Sanity check, in a previous version of this test, we were still RESUMED at this point.
- assertThat(lifecycleOwner.getLifecycle().getCurrentState(),
- is(Lifecycle.State.STARTED));
-
// Change the LiveData's value and verify that the observer's onChanged method is called
// since we are in the STARTED state.
mActivityTestRule.runOnUiThread(() -> mutableLiveData.setValue(1));
assertThat(atomicInteger.get(), is(1));
}
+
+ private FragmentActivity launchDialog() throws Throwable {
+ Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor(
+ NavigationDialogActivity.class.getCanonicalName(), null, false);
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ instrumentation.addMonitor(monitor);
+
+ FragmentActivity activity = mActivityTestRule.getActivity();
+ // helps with less flaky API 16 tests
+ Intent intent = new Intent(activity, NavigationDialogActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
+ intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ activity.startActivity(intent);
+ FragmentActivity fragmentActivity = (FragmentActivity) monitor.waitForActivity();
+ TestUtils.waitTillResumed(fragmentActivity, mActivityTestRule);
+ return fragmentActivity;
+ }
}
diff --git a/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/PartiallyCoveredActivityTest.java b/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/PartiallyCoveredActivityTest.java
new file mode 100644
index 0000000..07a9dc5
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/PartiallyCoveredActivityTest.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
+import static android.arch.lifecycle.Lifecycle.Event.ON_START;
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+import static android.arch.lifecycle.TestUtils.OrderedTuples.CREATE;
+import static android.arch.lifecycle.TestUtils.OrderedTuples.DESTROY;
+import static android.arch.lifecycle.TestUtils.OrderedTuples.PAUSE;
+import static android.arch.lifecycle.TestUtils.OrderedTuples.RESUME;
+import static android.arch.lifecycle.TestUtils.OrderedTuples.START;
+import static android.arch.lifecycle.TestUtils.OrderedTuples.STOP;
+import static android.arch.lifecycle.TestUtils.flatMap;
+import static android.arch.lifecycle.testapp.TestEvent.LIFECYCLE_EVENT;
+import static android.arch.lifecycle.testapp.TestEvent.OWNER_CALLBACK;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.singletonList;
+
+import android.app.Instrumentation;
+import android.arch.lifecycle.testapp.CollectingLifecycleOwner;
+import android.arch.lifecycle.testapp.CollectingSupportActivity;
+import android.arch.lifecycle.testapp.CollectingSupportFragment;
+import android.arch.lifecycle.testapp.NavigationDialogActivity;
+import android.arch.lifecycle.testapp.TestEvent;
+import android.content.Intent;
+import android.os.Build;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.util.Pair;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+
+/**
+ * Runs tests about the state when an activity is partially covered by another activity. Pre
+ * API 24, framework behavior changes so the test rely on whether state is saved or not and makes
+ * assertions accordingly.
+ */
+@SuppressWarnings("unchecked")
+@RunWith(Parameterized.class)
+@LargeTest
+public class PartiallyCoveredActivityTest {
+ private static final List[] IF_SAVED = new List[]{
+ // when overlaid
+ flatMap(CREATE, START, RESUME, PAUSE,
+ singletonList(new Pair<>(LIFECYCLE_EVENT, ON_STOP))),
+ // post dialog dismiss
+ asList(new Pair<>(OWNER_CALLBACK, ON_RESUME),
+ new Pair<>(LIFECYCLE_EVENT, ON_START),
+ new Pair<>(LIFECYCLE_EVENT, ON_RESUME)),
+ // post finish
+ flatMap(PAUSE, STOP, DESTROY)};
+
+ private static final List[] IF_NOT_SAVED = new List[]{
+ // when overlaid
+ flatMap(CREATE, START, RESUME, PAUSE),
+ // post dialog dismiss
+ flatMap(RESUME),
+ // post finish
+ flatMap(PAUSE, STOP, DESTROY)};
+
+ private static final boolean sShouldSave = Build.VERSION.SDK_INT < Build.VERSION_CODES.N;
+ private static final List<Pair<TestEvent, Lifecycle.Event>>[] EXPECTED =
+ sShouldSave ? IF_SAVED : IF_NOT_SAVED;
+
+ @Rule
+ public ActivityTestRule<CollectingSupportActivity> activityRule =
+ new ActivityTestRule<CollectingSupportActivity>(
+ CollectingSupportActivity.class) {
+ @Override
+ protected Intent getActivityIntent() {
+ // helps with less flaky API 16 tests
+ Intent intent = new Intent(InstrumentationRegistry.getTargetContext(),
+ CollectingSupportActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ return intent;
+ }
+ };
+ private final boolean mDismissDialog;
+
+ @Parameterized.Parameters(name = "dismissDialog_{0}")
+ public static List<Boolean> dismissDialog() {
+ return asList(true, false);
+ }
+
+ public PartiallyCoveredActivityTest(boolean dismissDialog) {
+ mDismissDialog = dismissDialog;
+ }
+
+ @Test
+ public void coveredWithDialog_activity() throws Throwable {
+ final CollectingSupportActivity activity = activityRule.getActivity();
+ runTest(activity);
+ }
+
+ @Test
+ public void coveredWithDialog_fragment() throws Throwable {
+ CollectingSupportFragment fragment = new CollectingSupportFragment();
+ activityRule.runOnUiThread(() -> activityRule.getActivity().replaceFragment(fragment));
+ runTest(fragment);
+ }
+
+ @Test
+ public void coveredWithDialog_childFragment() throws Throwable {
+ CollectingSupportFragment parentFragment = new CollectingSupportFragment();
+ CollectingSupportFragment childFragment = new CollectingSupportFragment();
+ activityRule.runOnUiThread(() -> {
+ activityRule.getActivity().replaceFragment(parentFragment);
+ parentFragment.replaceFragment(childFragment);
+ });
+ runTest(childFragment);
+ }
+
+ private void runTest(CollectingLifecycleOwner owner) throws Throwable {
+ TestUtils.waitTillResumed(owner, activityRule);
+ FragmentActivity dialog = launchDialog();
+ assertStateSaving();
+ waitForIdle();
+ assertThat(owner.copyCollectedEvents(), is(EXPECTED[0]));
+ List<Pair<TestEvent, Lifecycle.Event>> expected;
+ if (mDismissDialog) {
+ dialog.finish();
+ TestUtils.waitTillResumed(activityRule.getActivity(), activityRule);
+ assertThat(owner.copyCollectedEvents(), is(flatMap(EXPECTED[0], EXPECTED[1])));
+ expected = flatMap(EXPECTED[0], EXPECTED[1], EXPECTED[2]);
+ } else {
+ expected = flatMap(CREATE, START, RESUME, PAUSE, STOP, DESTROY);
+ }
+ CollectingSupportActivity activity = activityRule.getActivity();
+ activityRule.finishActivity();
+ TestUtils.waitTillDestroyed(activity, activityRule);
+ assertThat(owner.copyCollectedEvents(), is(expected));
+ }
+
+ // test sanity
+ private void assertStateSaving() throws ExecutionException, InterruptedException {
+ final CollectingSupportActivity activity = activityRule.getActivity();
+ if (sShouldSave) {
+ // state should be saved. wait for it to be saved
+ assertThat("test sanity",
+ activity.waitForStateSave(20), is(true));
+ assertThat("test sanity", activity.getSupportFragmentManager()
+ .isStateSaved(), is(true));
+ } else {
+ // should should not be saved
+ assertThat("test sanity", activity.getSupportFragmentManager()
+ .isStateSaved(), is(false));
+ }
+ }
+
+ private void waitForIdle() {
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ }
+
+ private FragmentActivity launchDialog() throws Throwable {
+ Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor(
+ NavigationDialogActivity.class.getCanonicalName(), null, false);
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ instrumentation.addMonitor(monitor);
+
+ FragmentActivity activity = activityRule.getActivity();
+
+ Intent intent = new Intent(activity, NavigationDialogActivity.class);
+ // disabling animations helps with less flaky API 16 tests
+ intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
+ intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ activity.startActivity(intent);
+ FragmentActivity fragmentActivity = (FragmentActivity) monitor.waitForActivity();
+ TestUtils.waitTillResumed(fragmentActivity, activityRule);
+ return fragmentActivity;
+ }
+}
diff --git a/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/TestUtils.java b/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/TestUtils.java
index ac35a59..f7f9bbe 100644
--- a/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/TestUtils.java
+++ b/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/TestUtils.java
@@ -16,15 +16,33 @@
package android.arch.lifecycle;
+import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY;
+import static android.arch.lifecycle.Lifecycle.Event.ON_PAUSE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
+import static android.arch.lifecycle.Lifecycle.Event.ON_START;
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+import static android.arch.lifecycle.Lifecycle.State.CREATED;
+import static android.arch.lifecycle.Lifecycle.State.DESTROYED;
import static android.arch.lifecycle.Lifecycle.State.RESUMED;
import static android.arch.lifecycle.Lifecycle.State.STARTED;
+import static android.arch.lifecycle.testapp.TestEvent.LIFECYCLE_EVENT;
+import static android.arch.lifecycle.testapp.TestEvent.OWNER_CALLBACK;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
import android.app.Activity;
import android.app.Instrumentation;
import android.app.Instrumentation.ActivityMonitor;
+import android.arch.lifecycle.testapp.TestEvent;
import android.support.test.InstrumentationRegistry;
import android.support.test.rule.ActivityTestRule;
+import android.support.v4.util.Pair;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -62,9 +80,9 @@
return result;
}
- static void waitTillResumed(final LifecycleOwner owner, ActivityTestRule<?> activityRule)
+ static void waitTillCreated(final LifecycleOwner owner, ActivityTestRule<?> activityRule)
throws Throwable {
- waitTillState(owner, activityRule, RESUMED);
+ waitTillState(owner, activityRule, CREATED);
}
static void waitTillStarted(final LifecycleOwner owner, ActivityTestRule<?> activityRule)
@@ -72,12 +90,23 @@
waitTillState(owner, activityRule, STARTED);
}
- private static void waitTillState(final LifecycleOwner owner, ActivityTestRule<?> activityRule,
+ static void waitTillResumed(final LifecycleOwner owner, ActivityTestRule<?> activityRule)
+ throws Throwable {
+ waitTillState(owner, activityRule, RESUMED);
+ }
+
+ static void waitTillDestroyed(final LifecycleOwner owner, ActivityTestRule<?> activityRule)
+ throws Throwable {
+ waitTillState(owner, activityRule, DESTROYED);
+ }
+
+ static void waitTillState(final LifecycleOwner owner, ActivityTestRule<?> activityRule,
Lifecycle.State state)
throws Throwable {
final CountDownLatch latch = new CountDownLatch(1);
activityRule.runOnUiThread(() -> {
- if (owner.getLifecycle().getCurrentState() == state) {
+ Lifecycle.State currentState = owner.getLifecycle().getCurrentState();
+ if (currentState == state) {
latch.countDown();
} else {
owner.getLifecycle().addObserver(new LifecycleObserver() {
@@ -91,7 +120,48 @@
});
}
});
- latch.await(1, TimeUnit.SECONDS);
+ boolean latchResult = latch.await(1, TimeUnit.MINUTES);
+ assertThat("expected " + state + " never happened. Current state:"
+ + owner.getLifecycle().getCurrentState(), latchResult, is(true));
+
+ // wait for another loop to ensure all observers are called
+ activityRule.runOnUiThread(() -> {
+ // do nothing
+ });
}
+ @SafeVarargs
+ static <T> List<T> flatMap(List<T>... items) {
+ ArrayList<T> result = new ArrayList<>();
+ for (List<T> item : items) {
+ result.addAll(item);
+ }
+ return result;
+ }
+
+ /**
+ * Event tuples of {@link TestEvent} and {@link Lifecycle.Event}
+ * in the order they should arrive.
+ */
+ @SuppressWarnings("unchecked")
+ static class OrderedTuples {
+ static final List<Pair<TestEvent, Lifecycle.Event>> CREATE =
+ Arrays.asList(new Pair(OWNER_CALLBACK, ON_CREATE),
+ new Pair(LIFECYCLE_EVENT, ON_CREATE));
+ static final List<Pair<TestEvent, Lifecycle.Event>> START =
+ Arrays.asList(new Pair(OWNER_CALLBACK, ON_START),
+ new Pair(LIFECYCLE_EVENT, ON_START));
+ static final List<Pair<TestEvent, Lifecycle.Event>> RESUME =
+ Arrays.asList(new Pair(OWNER_CALLBACK, ON_RESUME),
+ new Pair(LIFECYCLE_EVENT, ON_RESUME));
+ static final List<Pair<TestEvent, Lifecycle.Event>> PAUSE =
+ Arrays.asList(new Pair(LIFECYCLE_EVENT, ON_PAUSE),
+ new Pair(OWNER_CALLBACK, ON_PAUSE));
+ static final List<Pair<TestEvent, Lifecycle.Event>> STOP =
+ Arrays.asList(new Pair(LIFECYCLE_EVENT, ON_STOP),
+ new Pair(OWNER_CALLBACK, ON_STOP));
+ static final List<Pair<TestEvent, Lifecycle.Event>> DESTROY =
+ Arrays.asList(new Pair(LIFECYCLE_EVENT, ON_DESTROY),
+ new Pair(OWNER_CALLBACK, ON_DESTROY));
+ }
}
diff --git a/lifecycle/integration-tests/testapp/src/main/AndroidManifest.xml b/lifecycle/integration-tests/testapp/src/main/AndroidManifest.xml
index a4508b7..5e1d0a0 100644
--- a/lifecycle/integration-tests/testapp/src/main/AndroidManifest.xml
+++ b/lifecycle/integration-tests/testapp/src/main/AndroidManifest.xml
@@ -15,12 +15,6 @@
<application android:allowBackup="true" android:label="Test App" android:supportsRtl="true"
tools:ignore="AllowBackup,GoogleAppIndexingWarning,MissingApplicationIcon">
- <activity android:name="android.arch.lifecycle.testapp.LifecycleObservableActivity">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
<activity android:name="android.arch.lifecycle.testapp.LifecycleTestActivity">
<intent-filter>
@@ -28,13 +22,7 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
- <activity android:name="android.arch.lifecycle.testapp.FullLifecycleTestActivity">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- <activity android:name="android.arch.lifecycle.testapp.SupportLifecycleRegistryActivity">
+ <activity android:name="android.arch.lifecycle.testapp.CollectingSupportActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
diff --git a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/CollectingActivity.java b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/CollectingLifecycleOwner.java
similarity index 76%
rename from lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/CollectingActivity.java
rename to lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/CollectingLifecycleOwner.java
index 6e243b6..4213cab 100644
--- a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/CollectingActivity.java
+++ b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/CollectingLifecycleOwner.java
@@ -17,21 +17,20 @@
package android.arch.lifecycle.testapp;
import android.arch.lifecycle.Lifecycle;
-import android.util.Pair;
+import android.arch.lifecycle.LifecycleOwner;
+import android.support.v4.util.Pair;
import java.util.List;
/**
* For activities that collect their events.
*/
-public interface CollectingActivity {
- long TIMEOUT = 5;
-
+public interface CollectingLifecycleOwner extends LifecycleOwner {
/**
- * Return collected events
+ * Return a copy of currently collected events
*
* @return The list of collected events.
* @throws InterruptedException
*/
- List<Pair<TestEvent, Lifecycle.Event>> waitForCollectedEvents() throws InterruptedException;
+ List<Pair<TestEvent, Lifecycle.Event>> copyCollectedEvents();
}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/CollectingSupportActivity.java b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/CollectingSupportActivity.java
new file mode 100644
index 0000000..f38d422
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/CollectingSupportActivity.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle.testapp;
+
+import static android.arch.lifecycle.testapp.TestEvent.OWNER_CALLBACK;
+
+import android.arch.lifecycle.Lifecycle.Event;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.util.Pair;
+import android.widget.FrameLayout;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * LifecycleRegistryOwner that extends FragmentActivity.
+ */
+public class CollectingSupportActivity extends FragmentActivity implements
+ CollectingLifecycleOwner {
+
+ private final List<Pair<TestEvent, Event>> mCollectedEvents = new ArrayList<>();
+ private TestObserver mTestObserver = new TestObserver(mCollectedEvents);
+ private CountDownLatch mSavedStateLatch = new CountDownLatch(1);
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ FrameLayout layout = new FrameLayout(this);
+ layout.setId(R.id.fragment_container);
+ setContentView(layout);
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Event.ON_CREATE));
+ getLifecycle().addObserver(mTestObserver);
+ }
+
+ /**
+ * replaces the main content fragment w/ the given fragment.
+ */
+ public void replaceFragment(Fragment fragment) {
+ getSupportFragmentManager()
+ .beginTransaction()
+ .add(R.id.fragment_container, fragment)
+ .commitNow();
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Event.ON_START));
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Event.ON_RESUME));
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Event.ON_DESTROY));
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Event.ON_STOP));
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Event.ON_PAUSE));
+ // helps with less flaky API 16 tests.
+ overridePendingTransition(0, 0);
+ }
+
+ @Override
+ public List<Pair<TestEvent, Event>> copyCollectedEvents() {
+ return new ArrayList<>(mCollectedEvents);
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ mSavedStateLatch.countDown();
+ }
+
+ /**
+ * Waits for onSaveInstanceState to be called.
+ */
+ public boolean waitForStateSave(@SuppressWarnings("SameParameterValue") int seconds)
+ throws InterruptedException {
+ return mSavedStateLatch.await(seconds, TimeUnit.SECONDS);
+ }
+}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/CollectingSupportFragment.java b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/CollectingSupportFragment.java
new file mode 100644
index 0000000..9bbbe16
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/CollectingSupportFragment.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.arch.lifecycle.testapp;
+
+import static android.arch.lifecycle.testapp.TestEvent.OWNER_CALLBACK;
+
+import android.annotation.SuppressLint;
+import android.arch.lifecycle.Lifecycle;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.support.v4.util.Pair;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A support fragment that collects all of its events.
+ */
+@SuppressLint("ValidFragment")
+public class CollectingSupportFragment extends Fragment implements CollectingLifecycleOwner {
+ private final List<Pair<TestEvent, Lifecycle.Event>> mCollectedEvents =
+ new ArrayList<>();
+ private TestObserver mTestObserver = new TestObserver(mCollectedEvents);
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_CREATE));
+ getLifecycle().addObserver(mTestObserver);
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ //noinspection ConstantConditions
+ FrameLayout layout = new FrameLayout(container.getContext());
+ layout.setId(R.id.child_fragment_container);
+ return layout;
+ }
+
+ /**
+ * Runs a replace fragment transaction with 'fragment' on this Fragment.
+ */
+ public void replaceFragment(Fragment fragment) {
+ getChildFragmentManager()
+ .beginTransaction()
+ .add(R.id.child_fragment_container, fragment)
+ .commitNow();
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_START));
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_RESUME));
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_DESTROY));
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_STOP));
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_PAUSE));
+ }
+
+ @Override
+ public List<Pair<TestEvent, Lifecycle.Event>> copyCollectedEvents() {
+ return new ArrayList<>(mCollectedEvents);
+ }
+}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/FrameworkLifecycleRegistryActivity.java b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/FrameworkLifecycleRegistryActivity.java
index d8f4fb3..cdf577c 100644
--- a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/FrameworkLifecycleRegistryActivity.java
+++ b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/FrameworkLifecycleRegistryActivity.java
@@ -16,27 +16,29 @@
package android.arch.lifecycle.testapp;
-import static android.arch.lifecycle.testapp.TestEvent.ACTIVITY_CALLBACK;
+import static android.arch.lifecycle.testapp.TestEvent.OWNER_CALLBACK;
import android.app.Activity;
import android.arch.lifecycle.Lifecycle;
import android.arch.lifecycle.LifecycleRegistry;
import android.arch.lifecycle.LifecycleRegistryOwner;
import android.os.Bundle;
-import android.util.Pair;
+import android.support.annotation.NonNull;
+import android.support.v4.util.Pair;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
/**
* LifecycleRegistryOwner that extends framework activity.
*/
+@SuppressWarnings("deprecation")
public class FrameworkLifecycleRegistryActivity extends Activity implements
- LifecycleRegistryOwner, CollectingActivity {
+ LifecycleRegistryOwner, CollectingLifecycleOwner {
private LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);
+ @NonNull
@Override
public LifecycleRegistry getLifecycle() {
return mLifecycleRegistry;
@@ -49,49 +51,43 @@
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_CREATE));
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_CREATE));
getLifecycle().addObserver(mTestObserver);
}
@Override
protected void onStart() {
super.onStart();
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_START));
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_START));
}
@Override
protected void onResume() {
super.onResume();
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_RESUME));
- finish();
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_RESUME));
}
@Override
protected void onDestroy() {
super.onDestroy();
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_DESTROY));
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_DESTROY));
mLatch.countDown();
}
@Override
protected void onStop() {
super.onStop();
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_STOP));
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_STOP));
}
@Override
protected void onPause() {
super.onPause();
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_PAUSE));
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_PAUSE));
}
- /**
- * awaits for all events and returns them.
- */
@Override
- public List<Pair<TestEvent, Lifecycle.Event>> waitForCollectedEvents()
- throws InterruptedException {
- mLatch.await(TIMEOUT, TimeUnit.SECONDS);
- return mCollectedEvents;
+ public List<Pair<TestEvent, Lifecycle.Event>> copyCollectedEvents() {
+ return new ArrayList<>(mCollectedEvents);
}
}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/FullLifecycleTestActivity.java b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/FullLifecycleTestActivity.java
deleted file mode 100644
index 5f33c28..0000000
--- a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/FullLifecycleTestActivity.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * 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 android.arch.lifecycle.testapp;
-
-import static android.arch.lifecycle.testapp.TestEvent.ACTIVITY_CALLBACK;
-
-import android.arch.lifecycle.Lifecycle;
-import android.os.Bundle;
-import android.support.v4.app.FragmentActivity;
-import android.util.Pair;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Activity for testing full lifecycle
- */
-public class FullLifecycleTestActivity extends FragmentActivity implements CollectingActivity {
-
- private List<Pair<TestEvent, Lifecycle.Event>> mCollectedEvents = new ArrayList<>();
- private TestObserver mTestObserver = new TestObserver(mCollectedEvents);
- private CountDownLatch mLatch = new CountDownLatch(1);
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_CREATE));
- getLifecycle().addObserver(mTestObserver);
- }
-
- @Override
- protected void onStart() {
- super.onStart();
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_START));
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_RESUME));
- finish();
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_DESTROY));
- mLatch.countDown();
- }
-
- @Override
- protected void onStop() {
- super.onStop();
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_STOP));
- }
-
- @Override
- protected void onPause() {
- super.onPause();
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_PAUSE));
- }
-
- /**
- * awaits for all events and returns them.
- */
- @Override
- public List<Pair<TestEvent, Lifecycle.Event>> waitForCollectedEvents()
- throws InterruptedException {
- mLatch.await(TIMEOUT, TimeUnit.SECONDS);
- return mCollectedEvents;
- }
-}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/LifecycleObservableActivity.java b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/LifecycleObservableActivity.java
deleted file mode 100644
index 970b926..0000000
--- a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/LifecycleObservableActivity.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle.testapp;
-
-import android.os.Bundle;
-import android.support.annotation.Nullable;
-import android.support.v4.app.Fragment;
-import android.support.v4.app.FragmentActivity;
-
-/**
- * Simple test activity
- */
-public class LifecycleObservableActivity
- extends FragmentActivity
- implements OnSaveInstanceStateObservable {
-
- private OnSaveInstanceStateListener mOnSaveInstanceStateListener;
-
- /**
- * Runs a replace fragment transaction with 'fragment' on this Activity.
- */
- public void replaceFragment(Fragment fragment) {
- getSupportFragmentManager()
- .beginTransaction()
- .add(R.id.activityFrameLayout, fragment)
- .commitNow();
- }
-
- @Override
- public void setOnSaveInstanceStateListener(
- OnSaveInstanceStateListener onSaveInstanceStateListener) {
- mOnSaveInstanceStateListener = onSaveInstanceStateListener;
- }
-
- @Override
- protected void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity);
- }
-
- @Override
- protected void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- if (mOnSaveInstanceStateListener != null) {
- mOnSaveInstanceStateListener.onSaveInstanceState();
- }
- }
-}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/LifecycleObservableFragment.java b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/LifecycleObservableFragment.java
deleted file mode 100644
index 04c0604..0000000
--- a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/LifecycleObservableFragment.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.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 test activity
- */
-public class LifecycleObservableFragment
- extends Fragment
- implements OnSaveInstanceStateObservable {
-
- private OnSaveInstanceStateListener mOnSaveInstanceStateListener;
-
- /**
- * Runs a replace fragment transaction with 'fragment' on this Fragment.
- */
- public void replaceFragment(Fragment fragment) {
- getChildFragmentManager()
- .beginTransaction()
- .add(R.id.fragmentFrameLayout, fragment)
- .commitNow();
- }
-
- @Override
- public void setOnSaveInstanceStateListener(
- OnSaveInstanceStateListener onSaveInstanceStateListener) {
- mOnSaveInstanceStateListener = onSaveInstanceStateListener;
- }
-
- @Nullable
- @Override
- public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
- @Nullable Bundle savedInstanceState) {
- return inflater.inflate(R.layout.fragment, container, false);
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- if (mOnSaveInstanceStateListener != null) {
- mOnSaveInstanceStateListener.onSaveInstanceState();
- }
- }
-}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/NavigationDialogActivity.java b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/NavigationDialogActivity.java
index 0ae9403..7d53528 100644
--- a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/NavigationDialogActivity.java
+++ b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/NavigationDialogActivity.java
@@ -22,4 +22,10 @@
* an activity with Dialog theme.
*/
public class NavigationDialogActivity extends FragmentActivity {
+ @Override
+ protected void onPause() {
+ super.onPause();
+ // helps with less flaky API 16 tests
+ overridePendingTransition(0, 0);
+ }
}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/OnSaveInstanceStateObservable.java b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/OnSaveInstanceStateObservable.java
deleted file mode 100644
index 3eaa2d8..0000000
--- a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/OnSaveInstanceStateObservable.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle.testapp;
-
-/**
- * Interface for LifecycleOwners that track their own state internally for test purposes.
- */
-public interface OnSaveInstanceStateObservable {
-
- /**
- * Sets a OnSaveInstanceStateListener on the OnSaveInstanceStateObservable.
- */
- void setOnSaveInstanceStateListener(OnSaveInstanceStateListener onSaveInstanceStateListener);
-}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/SupportLifecycleRegistryActivity.java b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/SupportLifecycleRegistryActivity.java
deleted file mode 100644
index c46c6d3..0000000
--- a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/SupportLifecycleRegistryActivity.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle.testapp;
-
-import static android.arch.lifecycle.testapp.TestEvent.ACTIVITY_CALLBACK;
-
-import android.arch.lifecycle.Lifecycle.Event;
-import android.arch.lifecycle.LifecycleRegistry;
-import android.arch.lifecycle.LifecycleRegistryOwner;
-import android.os.Bundle;
-import android.support.v4.app.FragmentActivity;
-import android.util.Pair;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * LifecycleRegistryOwner that extends FragmentActivity.
- */
-public class SupportLifecycleRegistryActivity extends FragmentActivity implements
- LifecycleRegistryOwner, CollectingActivity {
- private LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);
- @Override
- public LifecycleRegistry getLifecycle() {
- return mLifecycleRegistry;
- }
-
- private List<Pair<TestEvent, Event>> mCollectedEvents = new ArrayList<>();
- private TestObserver mTestObserver = new TestObserver(mCollectedEvents);
- private CountDownLatch mLatch = new CountDownLatch(1);
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Event.ON_CREATE));
- getLifecycle().addObserver(mTestObserver);
- }
-
- @Override
- protected void onStart() {
- super.onStart();
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Event.ON_START));
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Event.ON_RESUME));
- finish();
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Event.ON_DESTROY));
- mLatch.countDown();
- }
-
- @Override
- protected void onStop() {
- super.onStop();
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Event.ON_STOP));
- }
-
- @Override
- protected void onPause() {
- super.onPause();
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Event.ON_PAUSE));
- }
-
- /**
- * awaits for all events and returns them.
- */
- @Override
- public List<Pair<TestEvent, Event>> waitForCollectedEvents() throws InterruptedException {
- mLatch.await(TIMEOUT, TimeUnit.SECONDS);
- return mCollectedEvents;
- }
-}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/TestEvent.java b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/TestEvent.java
index 0929f84..788045a 100644
--- a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/TestEvent.java
+++ b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/TestEvent.java
@@ -17,6 +17,6 @@
package android.arch.lifecycle.testapp;
public enum TestEvent {
- ACTIVITY_CALLBACK,
- LIFECYCLE_EVENT
+ OWNER_CALLBACK,
+ LIFECYCLE_EVENT,
}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/TestObserver.java b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/TestObserver.java
index c611239..00b8e16 100644
--- a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/TestObserver.java
+++ b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/TestObserver.java
@@ -28,7 +28,7 @@
import android.arch.lifecycle.LifecycleObserver;
import android.arch.lifecycle.LifecycleOwner;
import android.arch.lifecycle.OnLifecycleEvent;
-import android.util.Pair;
+import android.support.v4.util.Pair;
import java.util.List;
diff --git a/lifecycle/integration-tests/testapp/src/main/res/layout/activity.xml b/lifecycle/integration-tests/testapp/src/main/res/values/values.xml
similarity index 76%
rename from lifecycle/integration-tests/testapp/src/main/res/layout/activity.xml
rename to lifecycle/integration-tests/testapp/src/main/res/values/values.xml
index 64ddf2e..5041a76 100644
--- a/lifecycle/integration-tests/testapp/src/main/res/layout/activity.xml
+++ b/lifecycle/integration-tests/testapp/src/main/res/values/values.xml
@@ -14,8 +14,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<FrameLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/activityFrameLayout"
- android:layout_width="match_parent"
- android:layout_height="match_parent"/>
+
+<resources>
+ <item name="child_fragment_container" type="id"/>
+</resources>
diff --git a/lifecycle/runtime/src/main/java/android/arch/lifecycle/LifecycleRegistry.java b/lifecycle/runtime/src/main/java/android/arch/lifecycle/LifecycleRegistry.java
index 83609ac..bf8aff7 100644
--- a/lifecycle/runtime/src/main/java/android/arch/lifecycle/LifecycleRegistry.java
+++ b/lifecycle/runtime/src/main/java/android/arch/lifecycle/LifecycleRegistry.java
@@ -29,6 +29,7 @@
import static android.arch.lifecycle.Lifecycle.State.STARTED;
import android.arch.core.internal.FastSafeIterableMap;
+import android.support.annotation.MainThread;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
@@ -99,14 +100,14 @@
}
/**
- * Only marks the current state as the given value. It doesn't dispatch any event to its
- * listeners.
+ * Moves the Lifecycle to the given state and dispatches necessary events to the observers.
*
* @param state new state
*/
@SuppressWarnings("WeakerAccess")
+ @MainThread
public void markState(@NonNull State state) {
- mState = state;
+ moveToState(state);
}
/**
@@ -118,7 +119,15 @@
* @param event The event that was received
*/
public void handleLifecycleEvent(@NonNull Lifecycle.Event event) {
- mState = getStateAfter(event);
+ State next = getStateAfter(event);
+ moveToState(next);
+ }
+
+ private void moveToState(State next) {
+ if (mState == next) {
+ return;
+ }
+ mState = next;
if (mHandlingEvent || mAddingObserverCounter != 0) {
mNewEventOccurred = true;
// we will figure out what to do on upper level.
diff --git a/lifecycle/runtime/src/main/java/android/arch/lifecycle/LifecycleRegistryOwner.java b/lifecycle/runtime/src/main/java/android/arch/lifecycle/LifecycleRegistryOwner.java
index 38eeb6d..0c67fef 100644
--- a/lifecycle/runtime/src/main/java/android/arch/lifecycle/LifecycleRegistryOwner.java
+++ b/lifecycle/runtime/src/main/java/android/arch/lifecycle/LifecycleRegistryOwner.java
@@ -16,6 +16,8 @@
package android.arch.lifecycle;
+import android.support.annotation.NonNull;
+
/**
* @deprecated Use {@code android.support.v7.app.AppCompatActivity}
* which extends {@link LifecycleOwner}, so there are no use cases for this class.
@@ -23,6 +25,7 @@
@SuppressWarnings({"WeakerAccess", "unused"})
@Deprecated
public interface LifecycleRegistryOwner extends LifecycleOwner {
+ @NonNull
@Override
LifecycleRegistry getLifecycle();
}
diff --git a/lifecycle/runtime/src/main/java/android/arch/lifecycle/ReportFragment.java b/lifecycle/runtime/src/main/java/android/arch/lifecycle/ReportFragment.java
index 3e4ece8..16a89ce 100644
--- a/lifecycle/runtime/src/main/java/android/arch/lifecycle/ReportFragment.java
+++ b/lifecycle/runtime/src/main/java/android/arch/lifecycle/ReportFragment.java
@@ -28,7 +28,6 @@
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public class ReportFragment extends Fragment {
-
private static final String REPORT_FRAGMENT_TAG = "android.arch.lifecycle"
+ ".LifecycleDispatcher.report_fragment_tag";
diff --git a/paging/runtime/src/androidTest/java/android/arch/paging/PagedListAdapterHelperTest.java b/paging/runtime/src/androidTest/java/android/arch/paging/PagedListAdapterHelperTest.java
index 3518540..963d047 100644
--- a/paging/runtime/src/androidTest/java/android/arch/paging/PagedListAdapterHelperTest.java
+++ b/paging/runtime/src/androidTest/java/android/arch/paging/PagedListAdapterHelperTest.java
@@ -21,6 +21,7 @@
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@@ -289,6 +290,64 @@
assertFalse(helper.getCurrentList().isImmutable());
}
+ @Test
+ public void itemCountUpdatedBeforeListUpdateCallbacks() {
+ // verify that itemCount is updated in the helper before dispatching ListUpdateCallbacks
+
+ final int[] expectedCount = new int[] { 0 };
+ // provides access to helper, which must be constructed after callback
+ final PagedListAdapterHelper[] helperAccessor = new PagedListAdapterHelper[] { null };
+
+ ListUpdateCallback callback = new ListUpdateCallback() {
+ @Override
+ public void onInserted(int position, int count) {
+ assertEquals(expectedCount[0], helperAccessor[0].getItemCount());
+ }
+
+ @Override
+ public void onRemoved(int position, int count) {
+ assertEquals(expectedCount[0], helperAccessor[0].getItemCount());
+ }
+
+ @Override
+ public void onMoved(int fromPosition, int toPosition) {
+ fail("not expected");
+ }
+
+ @Override
+ public void onChanged(int position, int count, Object payload) {
+ fail("not expected");
+ }
+ };
+
+ PagedListAdapterHelper<String> helper = createHelper(callback, STRING_DIFF_CALLBACK);
+ helperAccessor[0] = helper;
+
+ PagedList.Config config = new PagedList.Config.Builder()
+ .setPageSize(20)
+ .build();
+
+
+ // in the fast-add case...
+ expectedCount[0] = 5;
+ assertEquals(0, helper.getItemCount());
+ helper.setList(createPagedListFromListAndPos(config, ALPHABET_LIST.subList(0, 5), 0));
+ assertEquals(5, helper.getItemCount());
+
+ // in the slow, diff on BG thread case...
+ expectedCount[0] = 10;
+ assertEquals(5, helper.getItemCount());
+ helper.setList(createPagedListFromListAndPos(config, ALPHABET_LIST.subList(0, 10), 0));
+ drain();
+ assertEquals(10, helper.getItemCount());
+
+ // and in the fast-remove case
+ expectedCount[0] = 0;
+ assertEquals(10, helper.getItemCount());
+ helper.setList(null);
+ assertEquals(0, helper.getItemCount());
+ }
+
private void drainExceptDiffThread() {
boolean executed;
do {
diff --git a/paging/runtime/src/main/java/android/arch/paging/PagedListAdapterHelper.java b/paging/runtime/src/main/java/android/arch/paging/PagedListAdapterHelper.java
index 0007a2e..abcff41 100644
--- a/paging/runtime/src/main/java/android/arch/paging/PagedListAdapterHelper.java
+++ b/paging/runtime/src/main/java/android/arch/paging/PagedListAdapterHelper.java
@@ -118,6 +118,8 @@
* @param <T> Type of the PagedLists this helper will receive.
*/
public class PagedListAdapterHelper<T> {
+ // updateCallback notifications must only be notified *after* new data and item count are stored
+ // this ensures Adapter#notifyItemRangeInserted etc are accessing the new data
private final ListUpdateCallback mUpdateCallback;
private final ListAdapterConfig<T> mConfig;
@@ -236,21 +238,25 @@
final int runGeneration = ++mMaxScheduledGeneration;
if (pagedList == null) {
- mUpdateCallback.onRemoved(0, getItemCount());
+ int removedCount = getItemCount();
if (mPagedList != null) {
mPagedList.removeWeakCallback(mPagedListCallback);
mPagedList = null;
} else if (mSnapshot != null) {
mSnapshot = null;
}
+ // dispatch update callback after updating mPagedList/mSnapshot
+ mUpdateCallback.onRemoved(0, removedCount);
return;
}
if (mPagedList == null && mSnapshot == null) {
// fast simple first insert
- mUpdateCallback.onInserted(0, pagedList.size());
mPagedList = pagedList;
pagedList.addWeakCallback(null, mPagedListCallback);
+
+ // dispatch update callback after updating mPagedList/mSnapshot
+ mUpdateCallback.onInserted(0, pagedList.size());
return;
}
@@ -296,10 +302,14 @@
throw new IllegalStateException("must be in snapshot state to apply diff");
}
- PagedStorageDiffHelper.dispatchDiff(mUpdateCallback,
- mSnapshot.mStorage, newList.mStorage, diffResult);
+ PagedList<T> previousSnapshot = mSnapshot;
mPagedList = newList;
mSnapshot = null;
+
+ // dispatch update callback after updating mPagedList/mSnapshot
+ PagedStorageDiffHelper.dispatchDiff(mUpdateCallback,
+ previousSnapshot.mStorage, newList.mStorage, diffResult);
+
newList.addWeakCallback(diffSnapshot, mPagedListCallback);
}
diff --git a/room/common/src/main/java/android/arch/persistence/room/Relation.java b/room/common/src/main/java/android/arch/persistence/room/Relation.java
index 7206699..d55bbfe 100644
--- a/room/common/src/main/java/android/arch/persistence/room/Relation.java
+++ b/room/common/src/main/java/android/arch/persistence/room/Relation.java
@@ -28,6 +28,8 @@
* <pre>
* {@literal @}Entity
* public class Pet {
+ * {@literal @} PrimaryKey
+ * int id;
* int userId;
* String name;
* // other fields
@@ -41,8 +43,8 @@
*
* {@literal @}Dao
* public interface UserPetDao {
- * {@literal @}Query("SELECT id, name from User WHERE age > :minAge")
- * public List<UserNameAndAllPets> loadUserAndPets(int minAge);
+ * {@literal @}Query("SELECT id, name from User")
+ * public List<UserNameAndAllPets> loadUserAndPets();
* }
* </pre>
* <p>
@@ -63,16 +65,16 @@
* {@literal @}Embedded
* public User user;
* {@literal @}Relation(parentColumn = "id", entityColumn = "userId", entity = Pet.class)
- * public List<PetNameAndId> pets;
+ * public List<PetNameAndId> pets;
* }
* {@literal @}Dao
* public interface UserPetDao {
- * {@literal @}Query("SELECT * from User WHERE age > :minAge")
- * public List<UserAllPets> loadUserAndPets(int minAge);
+ * {@literal @}Query("SELECT * from User")
+ * public List<UserAllPets> loadUserAndPets();
* }
* </pre>
* <p>
- * In the example above, {@code PetNameAndId} is a regular but all of fields are fetched
+ * In the example above, {@code PetNameAndId} is a regular Pojo but all of fields are fetched
* from the {@code entity} defined in the {@code @Relation} annotation (<i>Pet</i>).
* {@code PetNameAndId} could also define its own relations all of which would also be fetched
* automatically.
@@ -85,7 +87,7 @@
* public User user;
* {@literal @}Relation(parentColumn = "id", entityColumn = "userId", entity = Pet.class,
* projection = {"name"})
- * public List<String> petNames;
+ * public List<String> petNames;
* }
* </pre>
* <p>
@@ -93,7 +95,7 @@
* cannot have relations. This is a design decision to avoid common pitfalls in {@link Entity}
* setups. You can read more about it in the main Room documentation. When loading data, you can
* simply work around this limitation by creating Pojo classes that extend the {@link Entity}.
- *
+ * <p>
* Note that the {@code @Relation} annotated field cannot be a constructor parameter, it must be
* public or have a public setter.
*/
diff --git a/samples/Support7Demos/src/main/res/layout/appcompat_widgets_buttons.xml b/samples/Support7Demos/src/main/res/layout/appcompat_widgets_buttons.xml
index 517f4fa..ba4c045 100644
--- a/samples/Support7Demos/src/main/res/layout/appcompat_widgets_buttons.xml
+++ b/samples/Support7Demos/src/main/res/layout/appcompat_widgets_buttons.xml
@@ -73,40 +73,82 @@
</RadioGroup>
<Button
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Button"/>
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Button"/>
<Button
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Button (small)"
- style="@style/Widget.AppCompat.Button.Small"/>
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:enabled="false"
+ android:text="Button disabled"/>
<Button
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Button (borderless)"
- style="@style/Widget.AppCompat.Button.Borderless"/>
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Button (small)"
+ style="@style/Widget.AppCompat.Button.Small"/>
<Button
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Button (borderless + colored)"
- style="@style/Widget.AppCompat.Button.Borderless.Colored"/>
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:enabled="false"
+ android:text="Button (small) disabled"
+ style="@style/Widget.AppCompat.Button.Small"/>
<Button
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Button (colored)"
- style="@style/Widget.AppCompat.Button.Colored"/>
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Button (borderless)"
+ style="@style/Widget.AppCompat.Button.Borderless"/>
<Button
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Button (colored + tinted)"
- app:backgroundTint="#00FF00"
- style="@style/Widget.AppCompat.Button.Colored"/>
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:enabled="false"
+ android:text="Button (borderless) disabled"
+ style="@style/Widget.AppCompat.Button.Borderless"/>
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Button (borderless + colored)"
+ style="@style/Widget.AppCompat.Button.Borderless.Colored"/>
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:enabled="false"
+ android:text="Button (borderless + colored) disabled"
+ style="@style/Widget.AppCompat.Button.Borderless.Colored"/>
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Button (colored)"
+ style="@style/Widget.AppCompat.Button.Colored"/>
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:enabled="false"
+ android:text="Button (colored) disabled"
+ style="@style/Widget.AppCompat.Button.Colored"/>
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Button (colored + tinted)"
+ app:backgroundTint="#00FF00"
+ style="@style/Widget.AppCompat.Button.Colored"/>
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:enabled="false"
+ android:text="Button (colored + tinted) disabled"
+ app:backgroundTint="#00FF00"
+ style="@style/Widget.AppCompat.Button.Colored"/>
<RatingBar
android:layout_width="wrap_content"
diff --git a/samples/SupportWearDemos/src/main/java/com/example/android/support/wear/app/AmbientModeDemo.java b/samples/SupportWearDemos/src/main/java/com/example/android/support/wear/app/AmbientModeDemo.java
index 70270d2..0e7d3de 100644
--- a/samples/SupportWearDemos/src/main/java/com/example/android/support/wear/app/AmbientModeDemo.java
+++ b/samples/SupportWearDemos/src/main/java/com/example/android/support/wear/app/AmbientModeDemo.java
@@ -37,7 +37,6 @@
setContentView(R.layout.ambient_demo);
mStateTextView = findViewById(R.id.ambient_text);
AmbientMode.AmbientController controller = AmbientMode.attachAmbientSupport(this);
- controller.setAutoResumeEnabled(true);
}
@Override
diff --git a/lifecycle/integration-tests/testapp/src/main/res/layout/activity.xml b/v7/appcompat/res/color-v21/abc_btn_colored_borderless_text_material.xml
similarity index 69%
copy from lifecycle/integration-tests/testapp/src/main/res/layout/activity.xml
copy to v7/appcompat/res/color-v21/abc_btn_colored_borderless_text_material.xml
index 64ddf2e..f5585e3 100644
--- a/lifecycle/integration-tests/testapp/src/main/res/layout/activity.xml
+++ b/v7/appcompat/res/color-v21/abc_btn_colored_borderless_text_material.xml
@@ -14,8 +14,10 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<FrameLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/activityFrameLayout"
- android:layout_width="match_parent"
- android:layout_height="match_parent"/>
+
+<!-- Used for the text of a borderless colored button. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:color="?android:attr/textColorSecondary"/>
+ <item android:color="?attr/colorAccent"/>
+</selector>
\ No newline at end of file
diff --git a/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatTextHelper.java b/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatTextHelper.java
index 51510aa..fa6196f 100644
--- a/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatTextHelper.java
+++ b/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatTextHelper.java
@@ -29,6 +29,7 @@
import android.support.annotation.NonNull;
import android.support.annotation.RequiresApi;
import android.support.annotation.RestrictTo;
+import android.support.v4.content.res.ResourcesCompat;
import android.support.v4.widget.TextViewCompat;
import android.support.v7.appcompat.R;
import android.text.method.PasswordTransformationMethod;
@@ -36,6 +37,8 @@
import android.util.TypedValue;
import android.widget.TextView;
+import java.lang.ref.WeakReference;
+
@RequiresApi(9)
class AppCompatTextHelper {
@@ -63,6 +66,7 @@
private int mStyle = Typeface.NORMAL;
private Typeface mFontTypeface;
+ private boolean mAsyncFontPending;
AppCompatTextHelper(TextView view) {
mView = view;
@@ -213,8 +217,23 @@
? R.styleable.TextAppearance_android_fontFamily
: R.styleable.TextAppearance_fontFamily;
if (!context.isRestricted()) {
+ final WeakReference<TextView> textViewWeak = new WeakReference<>(mView);
+ ResourcesCompat.FontCallback replyCallback = new ResourcesCompat.FontCallback() {
+ @Override
+ public void onFontRetrieved(@NonNull Typeface typeface) {
+ onAsyncTypefaceReceived(textViewWeak, typeface);
+ }
+
+ @Override
+ public void onFontRetrievalFailed(int reason) {
+ // Do nothing.
+ }
+ };
try {
- mFontTypeface = a.getFont(fontFamilyId, mStyle);
+ // Note the callback will be triggered on the UI thread.
+ mFontTypeface = a.getFont(fontFamilyId, mStyle, replyCallback);
+ // If this call gave us an immediate result, ignore any pending callbacks.
+ mAsyncFontPending = mFontTypeface == null;
} catch (UnsupportedOperationException | Resources.NotFoundException e) {
// Expected if it is not a font resource.
}
@@ -222,12 +241,16 @@
if (mFontTypeface == null) {
// Try with String. This is done by TextView JB+, but fails in ICS
String fontFamilyName = a.getString(fontFamilyId);
- mFontTypeface = Typeface.create(fontFamilyName, mStyle);
+ if (fontFamilyName != null) {
+ mFontTypeface = Typeface.create(fontFamilyName, mStyle);
+ }
}
return;
}
if (a.hasValue(R.styleable.TextAppearance_android_typeface)) {
+ // Ignore previous pending fonts
+ mAsyncFontPending = false;
int typefaceIndex = a.getInt(R.styleable.TextAppearance_android_typeface, SANS);
switch (typefaceIndex) {
case SANS:
@@ -245,6 +268,16 @@
}
}
+ private void onAsyncTypefaceReceived(WeakReference<TextView> textViewWeak, Typeface typeface) {
+ if (mAsyncFontPending) {
+ mFontTypeface = typeface;
+ final TextView textView = textViewWeak.get();
+ if (textView != null) {
+ textView.setTypeface(typeface, mStyle);
+ }
+ }
+ }
+
void onSetTextAppearance(Context context, int resId) {
final TintTypedArray a = TintTypedArray.obtainStyledAttributes(context,
resId, R.styleable.TextAppearance);
diff --git a/v7/appcompat/src/main/java/android/support/v7/widget/TintTypedArray.java b/v7/appcompat/src/main/java/android/support/v7/widget/TintTypedArray.java
index 2270955..384c461 100644
--- a/v7/appcompat/src/main/java/android/support/v7/widget/TintTypedArray.java
+++ b/v7/appcompat/src/main/java/android/support/v7/widget/TintTypedArray.java
@@ -106,7 +106,8 @@
* not a font resource.
*/
@Nullable
- public Typeface getFont(@StyleableRes int index, int style) {
+ public Typeface getFont(@StyleableRes int index, int style,
+ @Nullable ResourcesCompat.FontCallback fontCallback) {
final int resourceId = mWrapped.getResourceId(index, 0);
if (resourceId == 0) {
return null;
@@ -114,7 +115,7 @@
if (mTypedValue == null) {
mTypedValue = new TypedValue();
}
- return ResourcesCompat.getFont(mContext, resourceId, mTypedValue, style);
+ return ResourcesCompat.getFont(mContext, resourceId, mTypedValue, style, fontCallback);
}
public int length() {
diff --git a/wear/api/27.0.0.txt b/wear/api/27.0.0.txt
index e397eb3..e9b7d86 100644
--- a/wear/api/27.0.0.txt
+++ b/wear/api/27.0.0.txt
@@ -21,7 +21,6 @@
public final class AmbientMode.AmbientController {
method public boolean isAmbient();
- method public void setAutoResumeEnabled(boolean);
}
}
diff --git a/wear/src/main/java/android/support/wear/ambient/AmbientMode.java b/wear/src/main/java/android/support/wear/ambient/AmbientMode.java
index 7fbbbb3..db53dfc 100644
--- a/wear/src/main/java/android/support/wear/ambient/AmbientMode.java
+++ b/wear/src/main/java/android/support/wear/ambient/AmbientMode.java
@@ -22,7 +22,6 @@
import android.os.Bundle;
import android.support.annotation.CallSuper;
import android.support.annotation.VisibleForTesting;
-import android.util.Log;
import com.google.android.wearable.compat.WearableActivityController;
@@ -263,20 +262,6 @@
AmbientController() {}
/**
- * Sets whether this activity's task should be moved to the front when the system exits
- * ambient mode. If true, the activity's task may be moved to the front if it was the last
- * activity to be running when ambient started, depending on how much time the system spent
- * in ambient mode.
- */
- public void setAutoResumeEnabled(boolean enabled) {
- if (mDelegate != null) {
- mDelegate.setAutoResumeEnabled(enabled);
- } else {
- Log.w(TAG, "The fragment is not yet fully initialized, this call is a no-op");
- }
- }
-
- /**
* @return {@code true} if the activity is currently in ambient.
*/
public boolean isAmbient() {
diff --git a/wear/tests/src/android/support/wear/ambient/AmbientModeTest.java b/wear/tests/src/android/support/wear/ambient/AmbientModeTest.java
index 155622d..301c513 100644
--- a/wear/tests/src/android/support/wear/ambient/AmbientModeTest.java
+++ b/wear/tests/src/android/support/wear/ambient/AmbientModeTest.java
@@ -76,17 +76,6 @@
}
@Test
- public void testControllerSetAutoResumeEnabled() {
- AmbientModeTestActivity activity = mActivityRule.getActivity();
-
- activity.getAmbientController().setAutoResumeEnabled(true);
- assertTrue(WearableActivityController.getLastInstance().isAutoResumeEnabled());
-
- activity.getAmbientController().setAutoResumeEnabled(false);
- assertFalse(WearableActivityController.getLastInstance().isAutoResumeEnabled());
- }
-
- @Test
public void testCallsControllerIsAmbient() {
AmbientModeTestActivity activity = mActivityRule.getActivity();