summaryrefslogtreecommitdiff
path: root/android/arch/lifecycle
diff options
context:
space:
mode:
Diffstat (limited to 'android/arch/lifecycle')
-rw-r--r--android/arch/lifecycle/ActivityFullLifecycleTest.java58
-rw-r--r--android/arch/lifecycle/AndroidViewModel.java7
-rw-r--r--android/arch/lifecycle/ClassesInfoCache.java16
-rw-r--r--android/arch/lifecycle/ComputableLiveData.java139
-rw-r--r--android/arch/lifecycle/DispatcherActivityCallbackTest.java77
-rw-r--r--android/arch/lifecycle/Lifecycle.java7
-rw-r--r--android/arch/lifecycle/LifecycleDispatcher.java182
-rw-r--r--android/arch/lifecycle/LifecycleOwner.java3
-rw-r--r--android/arch/lifecycle/LifecycleRegistry.java63
-rw-r--r--android/arch/lifecycle/LifecycleRegistryOwner.java3
-rw-r--r--android/arch/lifecycle/LifecycleRegistryTest.java19
-rw-r--r--android/arch/lifecycle/LiveData.java410
-rw-r--r--android/arch/lifecycle/LiveDataOnSaveInstanceStateTest.java177
-rw-r--r--android/arch/lifecycle/LiveDataTest.java224
-rw-r--r--android/arch/lifecycle/MediatorLiveData.java39
-rw-r--r--android/arch/lifecycle/MissingClassTest.java44
-rw-r--r--android/arch/lifecycle/PartiallyCoveredActivityTest.java200
-rw-r--r--android/arch/lifecycle/ProcessLifecycleOwner.java5
-rw-r--r--android/arch/lifecycle/ProcessLifecycleOwnerInitializer.java (renamed from android/arch/lifecycle/LifecycleRuntimeTrojanProvider.java)3
-rw-r--r--android/arch/lifecycle/ProcessOwnerTest.java24
-rw-r--r--android/arch/lifecycle/ReportFragment.java1
-rw-r--r--android/arch/lifecycle/TestUtils.java108
-rw-r--r--android/arch/lifecycle/Transformations.java8
-rw-r--r--android/arch/lifecycle/ViewModelProvider.java11
-rw-r--r--android/arch/lifecycle/ViewModelProviders.java3
-rw-r--r--android/arch/lifecycle/ViewModelStoreOwner.java3
-rw-r--r--android/arch/lifecycle/ViewModelStores.java5
-rw-r--r--android/arch/lifecycle/testapp/CollectingLifecycleOwner.java (renamed from android/arch/lifecycle/testapp/CollectingActivity.java)11
-rw-r--r--android/arch/lifecycle/testapp/CollectingSupportActivity.java113
-rw-r--r--android/arch/lifecycle/testapp/CollectingSupportFragment.java104
-rw-r--r--android/arch/lifecycle/testapp/FrameworkLifecycleRegistryActivity.java32
-rw-r--r--android/arch/lifecycle/testapp/FullLifecycleTestActivity.java88
-rw-r--r--android/arch/lifecycle/testapp/MainActivity.java31
-rw-r--r--android/arch/lifecycle/testapp/NavigationDialogActivity.java6
-rw-r--r--android/arch/lifecycle/testapp/NonSupportActivity.java85
-rw-r--r--android/arch/lifecycle/testapp/SupportLifecycleRegistryActivity.java95
-rw-r--r--android/arch/lifecycle/testapp/TestEvent.java4
-rw-r--r--android/arch/lifecycle/testapp/TestObserver.java2
38 files changed, 1804 insertions, 606 deletions
diff --git a/android/arch/lifecycle/ActivityFullLifecycleTest.java b/android/arch/lifecycle/ActivityFullLifecycleTest.java
index ee4e661a..78dd0150 100644
--- a/android/arch/lifecycle/ActivityFullLifecycleTest.java
+++ b/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 @@ public class ActivityFullLifecycleTest {
@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/android/arch/lifecycle/AndroidViewModel.java b/android/arch/lifecycle/AndroidViewModel.java
index 2c7e1739..106b2ef0 100644
--- a/android/arch/lifecycle/AndroidViewModel.java
+++ b/android/arch/lifecycle/AndroidViewModel.java
@@ -16,7 +16,9 @@
package android.arch.lifecycle;
+import android.annotation.SuppressLint;
import android.app.Application;
+import android.support.annotation.NonNull;
/**
* Application context aware {@link ViewModel}.
@@ -25,16 +27,19 @@ import android.app.Application;
* <p>
*/
public class AndroidViewModel extends ViewModel {
+ @SuppressLint("StaticFieldLeak")
private Application mApplication;
- public AndroidViewModel(Application application) {
+ public AndroidViewModel(@NonNull Application application) {
mApplication = application;
}
/**
* Return the application.
*/
+ @NonNull
public <T extends Application> T getApplication() {
+ //noinspection unchecked
return (T) mApplication;
}
}
diff --git a/android/arch/lifecycle/ClassesInfoCache.java b/android/arch/lifecycle/ClassesInfoCache.java
index f077daed..d88e2762 100644
--- a/android/arch/lifecycle/ClassesInfoCache.java
+++ b/android/arch/lifecycle/ClassesInfoCache.java
@@ -46,7 +46,7 @@ class ClassesInfoCache {
return mHasLifecycleMethods.get(klass);
}
- Method[] methods = klass.getDeclaredMethods();
+ Method[] methods = getDeclaredMethods(klass);
for (Method method : methods) {
OnLifecycleEvent annotation = method.getAnnotation(OnLifecycleEvent.class);
if (annotation != null) {
@@ -64,6 +64,18 @@ class ClassesInfoCache {
return false;
}
+ private Method[] getDeclaredMethods(Class klass) {
+ try {
+ return klass.getDeclaredMethods();
+ } catch (NoClassDefFoundError e) {
+ throw new IllegalArgumentException("The observer class has some methods that use "
+ + "newer APIs which are not available in the current OS version. Lifecycles "
+ + "cannot access even other methods so you should make sure that your "
+ + "observer classes only access framework classes that are available "
+ + "in your min API level OR use lifecycle:compiler annotation processor.", e);
+ }
+ }
+
CallbackInfo getInfo(Class klass) {
CallbackInfo existing = mCallbackMap.get(klass);
if (existing != null) {
@@ -106,7 +118,7 @@ class ClassesInfoCache {
}
}
- Method[] methods = declaredMethods != null ? declaredMethods : klass.getDeclaredMethods();
+ Method[] methods = declaredMethods != null ? declaredMethods : getDeclaredMethods(klass);
boolean hasLifecycleMethods = false;
for (Method method : methods) {
OnLifecycleEvent annotation = method.getAnnotation(OnLifecycleEvent.class);
diff --git a/android/arch/lifecycle/ComputableLiveData.java b/android/arch/lifecycle/ComputableLiveData.java
index f1352446..1ddcb1a9 100644
--- a/android/arch/lifecycle/ComputableLiveData.java
+++ b/android/arch/lifecycle/ComputableLiveData.java
@@ -1,9 +1,136 @@
-//ComputableLiveData interface for tests
+/*
+ * 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 android.arch.lifecycle.LiveData;
+
+import android.arch.core.executor.ArchTaskExecutor;
+import android.support.annotation.MainThread;
+import android.support.annotation.NonNull;
+import android.support.annotation.RestrictTo;
+import android.support.annotation.VisibleForTesting;
+import android.support.annotation.WorkerThread;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * A LiveData class that can be invalidated & computed on demand.
+ * <p>
+ * This is an internal class for now, might be public if we see the necessity.
+ *
+ * @param <T> The type of the live data
+ * @hide internal
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public abstract class ComputableLiveData<T> {
- public ComputableLiveData(){}
- abstract protected T compute();
- public LiveData<T> getLiveData() {return null;}
- public void invalidate() {}
+
+ private final LiveData<T> mLiveData;
+
+ private AtomicBoolean mInvalid = new AtomicBoolean(true);
+ private AtomicBoolean mComputing = new AtomicBoolean(false);
+
+ /**
+ * Creates a computable live data which is computed when there are active observers.
+ * <p>
+ * It can also be invalidated via {@link #invalidate()} which will result in a call to
+ * {@link #compute()} if there are active observers (or when they start observing)
+ */
+ @SuppressWarnings("WeakerAccess")
+ public ComputableLiveData() {
+ mLiveData = new LiveData<T>() {
+ @Override
+ protected void onActive() {
+ // TODO if we make this class public, we should accept an executor
+ ArchTaskExecutor.getInstance().executeOnDiskIO(mRefreshRunnable);
+ }
+ };
+ }
+
+ /**
+ * Returns the LiveData managed by this class.
+ *
+ * @return A LiveData that is controlled by ComputableLiveData.
+ */
+ @SuppressWarnings("WeakerAccess")
+ @NonNull
+ public LiveData<T> getLiveData() {
+ return mLiveData;
+ }
+
+ @VisibleForTesting
+ final Runnable mRefreshRunnable = new Runnable() {
+ @WorkerThread
+ @Override
+ public void run() {
+ boolean computed;
+ do {
+ computed = false;
+ // compute can happen only in 1 thread but no reason to lock others.
+ if (mComputing.compareAndSet(false, true)) {
+ // as long as it is invalid, keep computing.
+ try {
+ T value = null;
+ while (mInvalid.compareAndSet(true, false)) {
+ computed = true;
+ value = compute();
+ }
+ if (computed) {
+ mLiveData.postValue(value);
+ }
+ } finally {
+ // release compute lock
+ mComputing.set(false);
+ }
+ }
+ // check invalid after releasing compute lock to avoid the following scenario.
+ // Thread A runs compute()
+ // Thread A checks invalid, it is false
+ // Main thread sets invalid to true
+ // Thread B runs, fails to acquire compute lock and skips
+ // Thread A releases compute lock
+ // We've left invalid in set state. The check below recovers.
+ } while (computed && mInvalid.get());
+ }
+ };
+
+ // invalidation check always happens on the main thread
+ @VisibleForTesting
+ final Runnable mInvalidationRunnable = new Runnable() {
+ @MainThread
+ @Override
+ public void run() {
+ boolean isActive = mLiveData.hasActiveObservers();
+ if (mInvalid.compareAndSet(false, true)) {
+ if (isActive) {
+ // TODO if we make this class public, we should accept an executor.
+ ArchTaskExecutor.getInstance().executeOnDiskIO(mRefreshRunnable);
+ }
+ }
+ }
+ };
+
+ /**
+ * Invalidates the LiveData.
+ * <p>
+ * When there are active observers, this will trigger a call to {@link #compute()}.
+ */
+ public void invalidate() {
+ ArchTaskExecutor.getInstance().executeOnMainThread(mInvalidationRunnable);
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ @WorkerThread
+ protected abstract T compute();
}
diff --git a/android/arch/lifecycle/DispatcherActivityCallbackTest.java b/android/arch/lifecycle/DispatcherActivityCallbackTest.java
deleted file mode 100644
index 86b25b60..00000000
--- a/android/arch/lifecycle/DispatcherActivityCallbackTest.java
+++ /dev/null
@@ -1,77 +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;
-
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.annotation.SuppressLint;
-import android.app.Activity;
-import android.app.Fragment;
-import android.app.FragmentTransaction;
-import android.os.Bundle;
-import android.support.v4.app.FragmentActivity;
-import android.support.v4.app.FragmentManager;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@RunWith(JUnit4.class)
-public class DispatcherActivityCallbackTest {
- @Test
- public void onCreateFrameworkActivity() {
- LifecycleDispatcher.DispatcherActivityCallback callback =
- new LifecycleDispatcher.DispatcherActivityCallback();
- Activity activity = mock(Activity.class);
- checkReportFragment(callback, activity);
- }
-
- @Test
- public void onCreateFragmentActivity() {
- LifecycleDispatcher.DispatcherActivityCallback callback =
- new LifecycleDispatcher.DispatcherActivityCallback();
- FragmentActivity activity = mock(FragmentActivity.class);
- FragmentManager fragmentManager = mock(FragmentManager.class);
- when(activity.getSupportFragmentManager()).thenReturn(fragmentManager);
-
- checkReportFragment(callback, activity);
-
- verify(activity).getSupportFragmentManager();
- verify(fragmentManager).registerFragmentLifecycleCallbacks(
- any(FragmentManager.FragmentLifecycleCallbacks.class), eq(true));
- }
-
- @SuppressLint("CommitTransaction")
- private void checkReportFragment(LifecycleDispatcher.DispatcherActivityCallback callback,
- Activity activity) {
- android.app.FragmentManager fm = mock(android.app.FragmentManager.class);
- FragmentTransaction transaction = mock(FragmentTransaction.class);
- when(activity.getFragmentManager()).thenReturn(fm);
- when(fm.beginTransaction()).thenReturn(transaction);
- when(transaction.add(any(Fragment.class), anyString())).thenReturn(transaction);
- callback.onActivityCreated(activity, mock(Bundle.class));
- verify(activity).getFragmentManager();
- verify(fm).beginTransaction();
- verify(transaction).add(any(ReportFragment.class), anyString());
- verify(transaction).commit();
- }
-}
diff --git a/android/arch/lifecycle/Lifecycle.java b/android/arch/lifecycle/Lifecycle.java
index 02db5ff9..c0a2090c 100644
--- a/android/arch/lifecycle/Lifecycle.java
+++ b/android/arch/lifecycle/Lifecycle.java
@@ -17,6 +17,7 @@
package android.arch.lifecycle;
import android.support.annotation.MainThread;
+import android.support.annotation.NonNull;
/**
* Defines an object that has an Android Lifecycle. {@link android.support.v4.app.Fragment Fragment}
@@ -83,7 +84,7 @@ public abstract class Lifecycle {
* @param observer The observer to notify.
*/
@MainThread
- public abstract void addObserver(LifecycleObserver observer);
+ public abstract void addObserver(@NonNull LifecycleObserver observer);
/**
* Removes the given observer from the observers list.
@@ -99,7 +100,7 @@ public abstract class Lifecycle {
* @param observer The observer to be removed.
*/
@MainThread
- public abstract void removeObserver(LifecycleObserver observer);
+ public abstract void removeObserver(@NonNull LifecycleObserver observer);
/**
* Returns the current state of the Lifecycle.
@@ -193,7 +194,7 @@ public abstract class Lifecycle {
* @param state State to compare with
* @return true if this State is greater or equal to the given {@code state}
*/
- public boolean isAtLeast(State state) {
+ public boolean isAtLeast(@NonNull State state) {
return compareTo(state) >= 0;
}
}
diff --git a/android/arch/lifecycle/LifecycleDispatcher.java b/android/arch/lifecycle/LifecycleDispatcher.java
deleted file mode 100644
index 9fdec959..00000000
--- a/android/arch/lifecycle/LifecycleDispatcher.java
+++ /dev/null
@@ -1,182 +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;
-
-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 android.app.Activity;
-import android.app.Application;
-import android.arch.lifecycle.Lifecycle.State;
-import android.content.Context;
-import android.os.Bundle;
-import android.support.annotation.VisibleForTesting;
-import android.support.v4.app.Fragment;
-import android.support.v4.app.FragmentActivity;
-import android.support.v4.app.FragmentManager;
-
-import java.util.Collection;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * When initialized, it hooks into the Activity callback of the Application and observes
- * Activities. It is responsible to hook in child-fragments to activities and fragments to report
- * their lifecycle events. Another responsibility of this class is to mark as stopped all lifecycle
- * providers related to an activity as soon it is not safe to run a fragment transaction in this
- * activity.
- */
-class LifecycleDispatcher {
-
- private static final String REPORT_FRAGMENT_TAG = "android.arch.lifecycle"
- + ".LifecycleDispatcher.report_fragment_tag";
-
- private static AtomicBoolean sInitialized = new AtomicBoolean(false);
-
- static void init(Context context) {
- if (sInitialized.getAndSet(true)) {
- return;
- }
- ((Application) context.getApplicationContext())
- .registerActivityLifecycleCallbacks(new DispatcherActivityCallback());
- }
-
- @SuppressWarnings("WeakerAccess")
- @VisibleForTesting
- static class DispatcherActivityCallback extends EmptyActivityLifecycleCallbacks {
- private final FragmentCallback mFragmentCallback;
-
- DispatcherActivityCallback() {
- mFragmentCallback = new FragmentCallback();
- }
-
- @Override
- public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
- if (activity instanceof FragmentActivity) {
- ((FragmentActivity) activity).getSupportFragmentManager()
- .registerFragmentLifecycleCallbacks(mFragmentCallback, true);
- }
- ReportFragment.injectIfNeededIn(activity);
- }
-
- @Override
- public void onActivityStopped(Activity activity) {
- if (activity instanceof FragmentActivity) {
- markState((FragmentActivity) activity, CREATED);
- }
- }
-
- @Override
- public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
- if (activity instanceof FragmentActivity) {
- markState((FragmentActivity) activity, CREATED);
- }
- }
- }
-
- @SuppressWarnings("WeakerAccess")
- public static class DestructionReportFragment extends Fragment {
- @Override
- public void onPause() {
- super.onPause();
- dispatch(ON_PAUSE);
- }
-
- @Override
- public void onStop() {
- super.onStop();
- dispatch(ON_STOP);
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- dispatch(ON_DESTROY);
- }
-
- protected void dispatch(Lifecycle.Event event) {
- dispatchIfLifecycleOwner(getParentFragment(), event);
- }
- }
-
- private static void markState(FragmentManager manager, State state) {
- Collection<Fragment> fragments = manager.getFragments();
- if (fragments == null) {
- return;
- }
- for (Fragment fragment : fragments) {
- if (fragment == null) {
- continue;
- }
- markStateIn(fragment, state);
- if (fragment.isAdded()) {
- markState(fragment.getChildFragmentManager(), state);
- }
- }
- }
-
- private static void markStateIn(Object object, State state) {
- if (object instanceof LifecycleRegistryOwner) {
- LifecycleRegistry registry = ((LifecycleRegistryOwner) object).getLifecycle();
- registry.markState(state);
- }
- }
-
- private static void markState(FragmentActivity activity, State state) {
- markStateIn(activity, state);
- markState(activity.getSupportFragmentManager(), state);
- }
-
- private static void dispatchIfLifecycleOwner(Fragment fragment, Lifecycle.Event event) {
- if (fragment instanceof LifecycleRegistryOwner) {
- ((LifecycleRegistryOwner) fragment).getLifecycle().handleLifecycleEvent(event);
- }
- }
-
- @SuppressWarnings("WeakerAccess")
- @VisibleForTesting
- static class FragmentCallback extends FragmentManager.FragmentLifecycleCallbacks {
-
- @Override
- public void onFragmentCreated(FragmentManager fm, Fragment f, Bundle savedInstanceState) {
- dispatchIfLifecycleOwner(f, ON_CREATE);
-
- if (!(f instanceof LifecycleRegistryOwner)) {
- return;
- }
-
- if (f.getChildFragmentManager().findFragmentByTag(REPORT_FRAGMENT_TAG) == null) {
- f.getChildFragmentManager().beginTransaction().add(new DestructionReportFragment(),
- REPORT_FRAGMENT_TAG).commit();
- }
- }
-
- @Override
- public void onFragmentStarted(FragmentManager fm, Fragment f) {
- dispatchIfLifecycleOwner(f, ON_START);
- }
-
- @Override
- public void onFragmentResumed(FragmentManager fm, Fragment f) {
- dispatchIfLifecycleOwner(f, ON_RESUME);
- }
- }
-}
diff --git a/android/arch/lifecycle/LifecycleOwner.java b/android/arch/lifecycle/LifecycleOwner.java
index 934cf3a2..068bac1b 100644
--- a/android/arch/lifecycle/LifecycleOwner.java
+++ b/android/arch/lifecycle/LifecycleOwner.java
@@ -16,6 +16,8 @@
package android.arch.lifecycle;
+import android.support.annotation.NonNull;
+
/**
* A class that has an Android lifecycle. These events can be used by custom components to
* handle lifecycle changes without implementing any code inside the Activity or the Fragment.
@@ -29,5 +31,6 @@ public interface LifecycleOwner {
*
* @return The lifecycle of the provider.
*/
+ @NonNull
Lifecycle getLifecycle();
}
diff --git a/android/arch/lifecycle/LifecycleRegistry.java b/android/arch/lifecycle/LifecycleRegistry.java
index b83e6b8a..bf8aff79 100644
--- a/android/arch/lifecycle/LifecycleRegistry.java
+++ b/android/arch/lifecycle/LifecycleRegistry.java
@@ -29,9 +29,12 @@ import static android.arch.lifecycle.Lifecycle.State.RESUMED;
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;
+import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map.Entry;
@@ -44,6 +47,8 @@ import java.util.Map.Entry;
*/
public class LifecycleRegistry extends Lifecycle {
+ private static final String LOG_TAG = "LifecycleRegistry";
+
/**
* Custom list that keeps observers and can handle removals / additions during traversal.
*
@@ -59,8 +64,12 @@ public class LifecycleRegistry extends Lifecycle {
private State mState;
/**
* The provider that owns this Lifecycle.
+ * Only WeakReference on LifecycleOwner is kept, so if somebody leaks Lifecycle, they won't leak
+ * the whole Fragment / Activity. However, to leak Lifecycle object isn't great idea neither,
+ * because it keeps strong references on all other listeners, so you'll leak all of them as
+ * well.
*/
- private final LifecycleOwner mLifecycleOwner;
+ private final WeakReference<LifecycleOwner> mLifecycleOwner;
private int mAddingObserverCounter = 0;
@@ -86,19 +95,19 @@ public class LifecycleRegistry extends Lifecycle {
* @param provider The owner LifecycleOwner
*/
public LifecycleRegistry(@NonNull LifecycleOwner provider) {
- mLifecycleOwner = provider;
+ mLifecycleOwner = new WeakReference<>(provider);
mState = INITIALIZED;
}
/**
- * 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")
- public void markState(State state) {
- mState = state;
+ @MainThread
+ public void markState(@NonNull State state) {
+ moveToState(state);
}
/**
@@ -109,8 +118,16 @@ public class LifecycleRegistry extends Lifecycle {
*
* @param event The event that was received
*/
- public void handleLifecycleEvent(Lifecycle.Event event) {
- mState = getStateAfter(event);
+ public void handleLifecycleEvent(@NonNull Lifecycle.Event 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.
@@ -140,7 +157,7 @@ public class LifecycleRegistry extends Lifecycle {
}
@Override
- public void addObserver(LifecycleObserver observer) {
+ public void addObserver(@NonNull LifecycleObserver observer) {
State initialState = mState == DESTROYED ? DESTROYED : INITIALIZED;
ObserverWithState statefulObserver = new ObserverWithState(observer, initialState);
ObserverWithState previous = mObserverMap.putIfAbsent(observer, statefulObserver);
@@ -148,15 +165,19 @@ public class LifecycleRegistry extends Lifecycle {
if (previous != null) {
return;
}
+ LifecycleOwner lifecycleOwner = mLifecycleOwner.get();
+ if (lifecycleOwner == null) {
+ // it is null we should be destroyed. Fallback quickly
+ return;
+ }
boolean isReentrance = mAddingObserverCounter != 0 || mHandlingEvent;
-
State targetState = calculateTargetState(observer);
mAddingObserverCounter++;
while ((statefulObserver.mState.compareTo(targetState) < 0
&& mObserverMap.contains(observer))) {
pushParentState(statefulObserver.mState);
- statefulObserver.dispatchEvent(mLifecycleOwner, upEvent(statefulObserver.mState));
+ statefulObserver.dispatchEvent(lifecycleOwner, upEvent(statefulObserver.mState));
popParentState();
// mState / subling may have been changed recalculate
targetState = calculateTargetState(observer);
@@ -178,7 +199,7 @@ public class LifecycleRegistry extends Lifecycle {
}
@Override
- public void removeObserver(LifecycleObserver observer) {
+ public void removeObserver(@NonNull LifecycleObserver observer) {
// we consciously decided not to send destruction events here in opposition to addObserver.
// Our reasons for that:
// 1. These events haven't yet happened at all. In contrast to events in addObservers, that
@@ -258,7 +279,7 @@ public class LifecycleRegistry extends Lifecycle {
throw new IllegalArgumentException("Unexpected state value " + state);
}
- private void forwardPass() {
+ private void forwardPass(LifecycleOwner lifecycleOwner) {
Iterator<Entry<LifecycleObserver, ObserverWithState>> ascendingIterator =
mObserverMap.iteratorWithAdditions();
while (ascendingIterator.hasNext() && !mNewEventOccurred) {
@@ -267,13 +288,13 @@ public class LifecycleRegistry extends Lifecycle {
while ((observer.mState.compareTo(mState) < 0 && !mNewEventOccurred
&& mObserverMap.contains(entry.getKey()))) {
pushParentState(observer.mState);
- observer.dispatchEvent(mLifecycleOwner, upEvent(observer.mState));
+ observer.dispatchEvent(lifecycleOwner, upEvent(observer.mState));
popParentState();
}
}
}
- private void backwardPass() {
+ private void backwardPass(LifecycleOwner lifecycleOwner) {
Iterator<Entry<LifecycleObserver, ObserverWithState>> descendingIterator =
mObserverMap.descendingIterator();
while (descendingIterator.hasNext() && !mNewEventOccurred) {
@@ -283,7 +304,7 @@ public class LifecycleRegistry extends Lifecycle {
&& mObserverMap.contains(entry.getKey()))) {
Event event = downEvent(observer.mState);
pushParentState(getStateAfter(event));
- observer.dispatchEvent(mLifecycleOwner, event);
+ observer.dispatchEvent(lifecycleOwner, event);
popParentState();
}
}
@@ -292,16 +313,22 @@ public class LifecycleRegistry extends Lifecycle {
// happens only on the top of stack (never in reentrance),
// so it doesn't have to take in account parents
private void sync() {
+ LifecycleOwner lifecycleOwner = mLifecycleOwner.get();
+ if (lifecycleOwner == null) {
+ Log.w(LOG_TAG, "LifecycleOwner is garbage collected, you shouldn't try dispatch "
+ + "new events from it.");
+ return;
+ }
while (!isSynced()) {
mNewEventOccurred = false;
// no need to check eldest for nullability, because isSynced does it for us.
if (mState.compareTo(mObserverMap.eldest().getValue().mState) < 0) {
- backwardPass();
+ backwardPass(lifecycleOwner);
}
Entry<LifecycleObserver, ObserverWithState> newest = mObserverMap.newest();
if (!mNewEventOccurred && newest != null
&& mState.compareTo(newest.getValue().mState) > 0) {
- forwardPass();
+ forwardPass(lifecycleOwner);
}
}
mNewEventOccurred = false;
diff --git a/android/arch/lifecycle/LifecycleRegistryOwner.java b/android/arch/lifecycle/LifecycleRegistryOwner.java
index 38eeb6d3..0c67fefe 100644
--- a/android/arch/lifecycle/LifecycleRegistryOwner.java
+++ b/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 @@ package android.arch.lifecycle;
@SuppressWarnings({"WeakerAccess", "unused"})
@Deprecated
public interface LifecycleRegistryOwner extends LifecycleOwner {
+ @NonNull
@Override
LifecycleRegistry getLifecycle();
}
diff --git a/android/arch/lifecycle/LifecycleRegistryTest.java b/android/arch/lifecycle/LifecycleRegistryTest.java
index 6506454d..2a7bbad2 100644
--- a/android/arch/lifecycle/LifecycleRegistryTest.java
+++ b/android/arch/lifecycle/LifecycleRegistryTest.java
@@ -566,6 +566,25 @@ public class LifecycleRegistryTest {
verify(observer).onCreate();
}
+ private static void forceGc() {
+ Runtime.getRuntime().gc();
+ Runtime.getRuntime().runFinalization();
+ Runtime.getRuntime().gc();
+ Runtime.getRuntime().runFinalization();
+ }
+
+ @Test
+ public void goneLifecycleOwner() {
+ fullyInitializeRegistry();
+ mLifecycleOwner = null;
+ forceGc();
+ TestObserver observer = mock(TestObserver.class);
+ mRegistry.addObserver(observer);
+ verify(observer, never()).onCreate();
+ verify(observer, never()).onStart();
+ verify(observer, never()).onResume();
+ }
+
private void dispatchEvent(Lifecycle.Event event) {
when(mLifecycle.getCurrentState()).thenReturn(LifecycleRegistry.getStateAfter(event));
mRegistry.handleLifecycleEvent(event);
diff --git a/android/arch/lifecycle/LiveData.java b/android/arch/lifecycle/LiveData.java
index 3aea6acb..5b09c32f 100644
--- a/android/arch/lifecycle/LiveData.java
+++ b/android/arch/lifecycle/LiveData.java
@@ -1,4 +1,410 @@
-//LiveData interface for tests
+/*
+ * 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;
-public class LiveData<T> {
+
+import static android.arch.lifecycle.Lifecycle.State.DESTROYED;
+import static android.arch.lifecycle.Lifecycle.State.STARTED;
+
+import android.arch.core.executor.ArchTaskExecutor;
+import android.arch.core.internal.SafeIterableMap;
+import android.arch.lifecycle.Lifecycle.State;
+import android.support.annotation.MainThread;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * LiveData is a data holder class that can be observed within a given lifecycle.
+ * This means that an {@link Observer} can be added in a pair with a {@link LifecycleOwner}, and
+ * this observer will be notified about modifications of the wrapped data only if the paired
+ * LifecycleOwner is in active state. LifecycleOwner is considered as active, if its state is
+ * {@link Lifecycle.State#STARTED} or {@link Lifecycle.State#RESUMED}. An observer added via
+ * {@link #observeForever(Observer)} is considered as always active and thus will be always notified
+ * about modifications. For those observers, you should manually call
+ * {@link #removeObserver(Observer)}.
+ *
+ * <p> An observer added with a Lifecycle will be automatically removed if the corresponding
+ * Lifecycle moves to {@link Lifecycle.State#DESTROYED} state. This is especially useful for
+ * activities and fragments where they can safely observe LiveData and not worry about leaks:
+ * they will be instantly unsubscribed when they are destroyed.
+ *
+ * <p>
+ * In addition, LiveData has {@link LiveData#onActive()} and {@link LiveData#onInactive()} methods
+ * to get notified when number of active {@link Observer}s change between 0 and 1.
+ * This allows LiveData to release any heavy resources when it does not have any Observers that
+ * are actively observing.
+ * <p>
+ * This class is designed to hold individual data fields of {@link ViewModel},
+ * but can also be used for sharing data between different modules in your application
+ * in a decoupled fashion.
+ *
+ * @param <T> The type of data held by this instance
+ * @see ViewModel
+ */
+@SuppressWarnings({"WeakerAccess", "unused"})
+// TODO: Thread checks are too strict right now, we may consider automatically moving them to main
+// thread.
+public abstract class LiveData<T> {
+ private final Object mDataLock = new Object();
+ static final int START_VERSION = -1;
+ private static final Object NOT_SET = new Object();
+
+ private static final LifecycleOwner ALWAYS_ON = new LifecycleOwner() {
+
+ private LifecycleRegistry mRegistry = init();
+
+ private LifecycleRegistry init() {
+ LifecycleRegistry registry = new LifecycleRegistry(this);
+ registry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
+ registry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+ registry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
+ return registry;
+ }
+
+ @Override
+ public Lifecycle getLifecycle() {
+ return mRegistry;
+ }
+ };
+
+ private SafeIterableMap<Observer<T>, LifecycleBoundObserver> mObservers =
+ new SafeIterableMap<>();
+
+ // how many observers are in active state
+ private int mActiveCount = 0;
+ private volatile Object mData = NOT_SET;
+ // when setData is called, we set the pending data and actual data swap happens on the main
+ // thread
+ private volatile Object mPendingData = NOT_SET;
+ private int mVersion = START_VERSION;
+
+ private boolean mDispatchingValue;
+ @SuppressWarnings("FieldCanBeLocal")
+ private boolean mDispatchInvalidated;
+ private final Runnable mPostValueRunnable = new Runnable() {
+ @Override
+ public void run() {
+ Object newValue;
+ synchronized (mDataLock) {
+ newValue = mPendingData;
+ mPendingData = NOT_SET;
+ }
+ //noinspection unchecked
+ setValue((T) newValue);
+ }
+ };
+
+ private void considerNotify(LifecycleBoundObserver observer) {
+ if (!observer.active) {
+ return;
+ }
+ // Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet.
+ //
+ // we still first check observer.active to keep it as the entrance for events. So even if
+ // the observer moved to an active state, if we've not received that event, we better not
+ // notify for a more predictable notification order.
+ if (!isActiveState(observer.owner.getLifecycle().getCurrentState())) {
+ observer.activeStateChanged(false);
+ return;
+ }
+ if (observer.lastVersion >= mVersion) {
+ return;
+ }
+ observer.lastVersion = mVersion;
+ //noinspection unchecked
+ observer.observer.onChanged((T) mData);
+ }
+
+ private void dispatchingValue(@Nullable LifecycleBoundObserver initiator) {
+ if (mDispatchingValue) {
+ mDispatchInvalidated = true;
+ return;
+ }
+ mDispatchingValue = true;
+ do {
+ mDispatchInvalidated = false;
+ if (initiator != null) {
+ considerNotify(initiator);
+ initiator = null;
+ } else {
+ for (Iterator<Map.Entry<Observer<T>, LifecycleBoundObserver>> iterator =
+ mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
+ considerNotify(iterator.next().getValue());
+ if (mDispatchInvalidated) {
+ break;
+ }
+ }
+ }
+ } while (mDispatchInvalidated);
+ mDispatchingValue = false;
+ }
+
+ /**
+ * Adds the given observer to the observers list within the lifespan of the given
+ * owner. The events are dispatched on the main thread. If LiveData already has data
+ * set, it will be delivered to the observer.
+ * <p>
+ * The observer will only receive events if the owner is in {@link Lifecycle.State#STARTED}
+ * or {@link Lifecycle.State#RESUMED} state (active).
+ * <p>
+ * If the owner moves to the {@link Lifecycle.State#DESTROYED} state, the observer will
+ * automatically be removed.
+ * <p>
+ * When data changes while the {@code owner} is not active, it will not receive any updates.
+ * If it becomes active again, it will receive the last available data automatically.
+ * <p>
+ * LiveData keeps a strong reference to the observer and the owner as long as the
+ * given LifecycleOwner is not destroyed. When it is destroyed, LiveData removes references to
+ * the observer &amp; the owner.
+ * <p>
+ * If the given owner is already in {@link Lifecycle.State#DESTROYED} state, LiveData
+ * ignores the call.
+ * <p>
+ * If the given owner, observer tuple is already in the list, the call is ignored.
+ * If the observer is already in the list with another owner, LiveData throws an
+ * {@link IllegalArgumentException}.
+ *
+ * @param owner The LifecycleOwner which controls the observer
+ * @param observer The observer that will receive the events
+ */
+ @MainThread
+ public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) {
+ if (owner.getLifecycle().getCurrentState() == DESTROYED) {
+ // ignore
+ return;
+ }
+ LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
+ LifecycleBoundObserver existing = mObservers.putIfAbsent(observer, wrapper);
+ if (existing != null && existing.owner != wrapper.owner) {
+ throw new IllegalArgumentException("Cannot add the same observer"
+ + " with different lifecycles");
+ }
+ if (existing != null) {
+ return;
+ }
+ owner.getLifecycle().addObserver(wrapper);
+ }
+
+ /**
+ * Adds the given observer to the observers list. This call is similar to
+ * {@link LiveData#observe(LifecycleOwner, Observer)} with a LifecycleOwner, which
+ * is always active. This means that the given observer will receive all events and will never
+ * be automatically removed. You should manually call {@link #removeObserver(Observer)} to stop
+ * observing this LiveData.
+ * While LiveData has one of such observers, it will be considered
+ * as active.
+ * <p>
+ * If the observer was already added with an owner to this LiveData, LiveData throws an
+ * {@link IllegalArgumentException}.
+ *
+ * @param observer The observer that will receive the events
+ */
+ @MainThread
+ public void observeForever(@NonNull Observer<T> observer) {
+ observe(ALWAYS_ON, observer);
+ }
+
+ /**
+ * Removes the given observer from the observers list.
+ *
+ * @param observer The Observer to receive events.
+ */
+ @MainThread
+ public void removeObserver(@NonNull final Observer<T> observer) {
+ assertMainThread("removeObserver");
+ LifecycleBoundObserver removed = mObservers.remove(observer);
+ if (removed == null) {
+ return;
+ }
+ removed.owner.getLifecycle().removeObserver(removed);
+ removed.activeStateChanged(false);
+ }
+
+ /**
+ * Removes all observers that are tied to the given {@link LifecycleOwner}.
+ *
+ * @param owner The {@code LifecycleOwner} scope for the observers to be removed.
+ */
+ @MainThread
+ public void removeObservers(@NonNull final LifecycleOwner owner) {
+ assertMainThread("removeObservers");
+ for (Map.Entry<Observer<T>, LifecycleBoundObserver> entry : mObservers) {
+ if (entry.getValue().owner == owner) {
+ removeObserver(entry.getKey());
+ }
+ }
+ }
+
+ /**
+ * Posts a task to a main thread to set the given value. So if you have a following code
+ * executed in the main thread:
+ * <pre class="prettyprint">
+ * liveData.postValue("a");
+ * liveData.setValue("b");
+ * </pre>
+ * The value "b" would be set at first and later the main thread would override it with
+ * the value "a".
+ * <p>
+ * If you called this method multiple times before a main thread executed a posted task, only
+ * the last value would be dispatched.
+ *
+ * @param value The new value
+ */
+ protected void postValue(T value) {
+ boolean postTask;
+ synchronized (mDataLock) {
+ postTask = mPendingData == NOT_SET;
+ mPendingData = value;
+ }
+ if (!postTask) {
+ return;
+ }
+ ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
+ }
+
+ /**
+ * Sets the value. If there are active observers, the value will be dispatched to them.
+ * <p>
+ * This method must be called from the main thread. If you need set a value from a background
+ * thread, you can use {@link #postValue(Object)}
+ *
+ * @param value The new value
+ */
+ @MainThread
+ protected void setValue(T value) {
+ assertMainThread("setValue");
+ mVersion++;
+ mData = value;
+ dispatchingValue(null);
+ }
+
+ /**
+ * Returns the current value.
+ * Note that calling this method on a background thread does not guarantee that the latest
+ * value set will be received.
+ *
+ * @return the current value
+ */
+ @Nullable
+ public T getValue() {
+ Object data = mData;
+ if (data != NOT_SET) {
+ //noinspection unchecked
+ return (T) data;
+ }
+ return null;
+ }
+
+ int getVersion() {
+ return mVersion;
+ }
+
+ /**
+ * Called when the number of active observers change to 1 from 0.
+ * <p>
+ * This callback can be used to know that this LiveData is being used thus should be kept
+ * up to date.
+ */
+ protected void onActive() {
+
+ }
+
+ /**
+ * Called when the number of active observers change from 1 to 0.
+ * <p>
+ * This does not mean that there are no observers left, there may still be observers but their
+ * lifecycle states aren't {@link Lifecycle.State#STARTED} or {@link Lifecycle.State#RESUMED}
+ * (like an Activity in the back stack).
+ * <p>
+ * You can check if there are observers via {@link #hasObservers()}.
+ */
+ protected void onInactive() {
+
+ }
+
+ /**
+ * Returns true if this LiveData has observers.
+ *
+ * @return true if this LiveData has observers
+ */
+ public boolean hasObservers() {
+ return mObservers.size() > 0;
+ }
+
+ /**
+ * Returns true if this LiveData has active observers.
+ *
+ * @return true if this LiveData has active observers
+ */
+ public boolean hasActiveObservers() {
+ return mActiveCount > 0;
+ }
+
+ class LifecycleBoundObserver implements GenericLifecycleObserver {
+ public final LifecycleOwner owner;
+ public final Observer<T> observer;
+ public boolean active;
+ public int lastVersion = START_VERSION;
+
+ LifecycleBoundObserver(LifecycleOwner owner, Observer<T> observer) {
+ this.owner = owner;
+ this.observer = observer;
+ }
+
+ @Override
+ public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
+ if (owner.getLifecycle().getCurrentState() == DESTROYED) {
+ removeObserver(observer);
+ return;
+ }
+ // immediately set active state, so we'd never dispatch anything to inactive
+ // owner
+ activeStateChanged(isActiveState(owner.getLifecycle().getCurrentState()));
+ }
+
+ void activeStateChanged(boolean newActive) {
+ if (newActive == active) {
+ return;
+ }
+ active = newActive;
+ boolean wasInactive = LiveData.this.mActiveCount == 0;
+ LiveData.this.mActiveCount += active ? 1 : -1;
+ if (wasInactive && active) {
+ onActive();
+ }
+ if (LiveData.this.mActiveCount == 0 && !active) {
+ onInactive();
+ }
+ if (active) {
+ dispatchingValue(this);
+ }
+ }
+ }
+
+ static boolean isActiveState(State state) {
+ return state.isAtLeast(STARTED);
+ }
+
+ private void assertMainThread(String methodName) {
+ if (!ArchTaskExecutor.getInstance().isMainThread()) {
+ throw new IllegalStateException("Cannot invoke " + methodName + " on a background"
+ + " thread");
+ }
+ }
}
diff --git a/android/arch/lifecycle/LiveDataOnSaveInstanceStateTest.java b/android/arch/lifecycle/LiveDataOnSaveInstanceStateTest.java
new file mode 100644
index 00000000..836cfff0
--- /dev/null
+++ b/android/arch/lifecycle/LiveDataOnSaveInstanceStateTest.java
@@ -0,0 +1,177 @@
+/*
+ * 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 org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.app.Instrumentation;
+import android.arch.lifecycle.testapp.CollectingSupportActivity;
+import android.arch.lifecycle.testapp.CollectingSupportFragment;
+import android.arch.lifecycle.testapp.NavigationDialogActivity;
+import android.content.Intent;
+import android.os.Build;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SdkSuppress;
+import android.support.test.filters.SmallTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.app.FragmentActivity;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class LiveDataOnSaveInstanceStateTest {
+ @Rule
+ public ActivityTestRule<CollectingSupportActivity> mActivityTestRule =
+ new ActivityTestRule<>(CollectingSupportActivity.class);
+
+ @Test
+ @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.M)
+ public void liveData_partiallyObscuredActivity_maxSdkM() throws Throwable {
+ CollectingSupportActivity activity = mActivityTestRule.getActivity();
+
+ liveData_partiallyObscuredLifecycleOwner_maxSdkM(activity);
+ }
+
+ @Test
+ @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.M)
+ public void liveData_partiallyObscuredActivityWithFragment_maxSdkM() throws Throwable {
+ CollectingSupportActivity activity = mActivityTestRule.getActivity();
+ CollectingSupportFragment fragment = new CollectingSupportFragment();
+ mActivityTestRule.runOnUiThread(() -> activity.replaceFragment(fragment));
+
+ liveData_partiallyObscuredLifecycleOwner_maxSdkM(fragment);
+ }
+
+ @Test
+ @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.M)
+ public void liveData_partiallyObscuredActivityFragmentInFragment_maxSdkM() throws Throwable {
+ CollectingSupportActivity activity = mActivityTestRule.getActivity();
+ CollectingSupportFragment fragment = new CollectingSupportFragment();
+ CollectingSupportFragment fragment2 = new CollectingSupportFragment();
+ mActivityTestRule.runOnUiThread(() -> {
+ activity.replaceFragment(fragment);
+ fragment.replaceFragment(fragment2);
+ });
+
+ liveData_partiallyObscuredLifecycleOwner_maxSdkM(fragment2);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N)
+ public void liveData_partiallyObscuredActivity_minSdkN() throws Throwable {
+ CollectingSupportActivity activity = mActivityTestRule.getActivity();
+
+ liveData_partiallyObscuredLifecycleOwner_minSdkN(activity);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N)
+ public void liveData_partiallyObscuredActivityWithFragment_minSdkN() throws Throwable {
+ CollectingSupportActivity activity = mActivityTestRule.getActivity();
+ CollectingSupportFragment fragment = new CollectingSupportFragment();
+ mActivityTestRule.runOnUiThread(() -> activity.replaceFragment(fragment));
+
+ liveData_partiallyObscuredLifecycleOwner_minSdkN(fragment);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N)
+ public void liveData_partiallyObscuredActivityFragmentInFragment_minSdkN() throws Throwable {
+ CollectingSupportActivity activity = mActivityTestRule.getActivity();
+ CollectingSupportFragment fragment = new CollectingSupportFragment();
+ CollectingSupportFragment fragment2 = new CollectingSupportFragment();
+ mActivityTestRule.runOnUiThread(() -> {
+ activity.replaceFragment(fragment);
+ fragment.replaceFragment(fragment2);
+ });
+
+ liveData_partiallyObscuredLifecycleOwner_minSdkN(fragment2);
+ }
+
+ private void liveData_partiallyObscuredLifecycleOwner_maxSdkM(LifecycleOwner lifecycleOwner)
+ throws Throwable {
+ final AtomicInteger atomicInteger = new AtomicInteger(0);
+ MutableLiveData<Integer> mutableLiveData = new MutableLiveData<>();
+ mActivityTestRule.runOnUiThread(() -> mutableLiveData.setValue(0));
+
+ TestUtils.waitTillResumed(lifecycleOwner, mActivityTestRule);
+
+ mutableLiveData.observe(lifecycleOwner, atomicInteger::set);
+
+ final FragmentActivity dialogActivity = launchDialog();
+
+ 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.
+ mActivityTestRule.runOnUiThread(() -> mutableLiveData.setValue(1));
+ assertThat(atomicInteger.get(), is(0));
+
+ // Finish the dialog Activity, wait for the main activity to be resumed, and assert that
+ // the observer's onChanged method is called.
+ mActivityTestRule.runOnUiThread(dialogActivity::finish);
+ TestUtils.waitTillResumed(lifecycleOwner, mActivityTestRule);
+ assertThat(atomicInteger.get(), is(1));
+ }
+
+ private void liveData_partiallyObscuredLifecycleOwner_minSdkN(LifecycleOwner lifecycleOwner)
+ throws Throwable {
+ final AtomicInteger atomicInteger = new AtomicInteger(0);
+ MutableLiveData<Integer> mutableLiveData = new MutableLiveData<>();
+ mActivityTestRule.runOnUiThread(() -> mutableLiveData.setValue(0));
+
+ TestUtils.waitTillResumed(lifecycleOwner, mActivityTestRule);
+
+ mutableLiveData.observe(lifecycleOwner, atomicInteger::set);
+
+ // 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).
+ launchDialog();
+ TestUtils.waitTillStarted(lifecycleOwner, mActivityTestRule);
+
+ // 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/android/arch/lifecycle/LiveDataTest.java b/android/arch/lifecycle/LiveDataTest.java
index 9f0b4257..647d5d7a 100644
--- a/android/arch/lifecycle/LiveDataTest.java
+++ b/android/arch/lifecycle/LiveDataTest.java
@@ -53,18 +53,29 @@ import org.mockito.Mockito;
public class LiveDataTest {
private PublicLiveData<String> mLiveData;
private LifecycleOwner mOwner;
+ private LifecycleOwner mOwner2;
private LifecycleRegistry mRegistry;
+ private LifecycleRegistry mRegistry2;
private MethodExec mActiveObserversChanged;
private boolean mInObserver;
@Before
public void init() {
mLiveData = new PublicLiveData<>();
+
+ mActiveObserversChanged = mock(MethodExec.class);
+ mLiveData.activeObserversChanged = mActiveObserversChanged;
+
mOwner = mock(LifecycleOwner.class);
+
mRegistry = new LifecycleRegistry(mOwner);
when(mOwner.getLifecycle()).thenReturn(mRegistry);
- mActiveObserversChanged = mock(MethodExec.class);
- mLiveData.activeObserversChanged = mActiveObserversChanged;
+
+ mOwner2 = mock(LifecycleOwner.class);
+
+ mRegistry2 = new LifecycleRegistry(mOwner2);
+ when(mOwner2.getLifecycle()).thenReturn(mRegistry2);
+
mInObserver = false;
}
@@ -159,14 +170,11 @@ public class LiveDataTest {
@Test
public void testAddSameObserverIn2LifecycleOwners() {
Observer<String> observer = (Observer<String>) mock(Observer.class);
- LifecycleOwner owner2 = mock(LifecycleOwner.class);
- LifecycleRegistry registry2 = new LifecycleRegistry(owner2);
- when(owner2.getLifecycle()).thenReturn(registry2);
mLiveData.observe(mOwner, observer);
Throwable throwable = null;
try {
- mLiveData.observe(owner2, observer);
+ mLiveData.observe(mOwner2, observer);
} catch (Throwable t) {
throwable = t;
}
@@ -456,6 +464,210 @@ public class LiveDataTest {
inOrder.verifyNoMoreInteractions();
}
+ @Test
+ public void setValue_neverActive_observerOnChangedNotCalled() {
+ Observer<String> observer = (Observer<String>) mock(Observer.class);
+ mLiveData.observe(mOwner, observer);
+
+ mLiveData.setValue("1");
+
+ verify(observer, never()).onChanged(anyString());
+ }
+
+ @Test
+ public void setValue_twoObserversTwoStartedOwners_onChangedCalledOnBoth() {
+ Observer<String> observer1 = mock(Observer.class);
+ Observer<String> observer2 = mock(Observer.class);
+
+ mLiveData.observe(mOwner, observer1);
+ mLiveData.observe(mOwner2, observer2);
+
+ mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+ mRegistry2.handleLifecycleEvent(Lifecycle.Event.ON_START);
+
+ mLiveData.setValue("1");
+
+ verify(observer1).onChanged("1");
+ verify(observer2).onChanged("1");
+ }
+
+ @Test
+ public void setValue_twoObserversOneStartedOwner_onChangedCalledOnOneCorrectObserver() {
+ Observer<String> observer1 = mock(Observer.class);
+ Observer<String> observer2 = mock(Observer.class);
+
+ mLiveData.observe(mOwner, observer1);
+ mLiveData.observe(mOwner2, observer2);
+
+ mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+
+ mLiveData.setValue("1");
+
+ verify(observer1).onChanged("1");
+ verify(observer2, never()).onChanged(anyString());
+ }
+
+ @Test
+ public void setValue_twoObserversBothStartedAfterSetValue_onChangedCalledOnBoth() {
+ Observer<String> observer1 = mock(Observer.class);
+ Observer<String> observer2 = mock(Observer.class);
+
+ mLiveData.observe(mOwner, observer1);
+ mLiveData.observe(mOwner2, observer2);
+
+ mLiveData.setValue("1");
+
+ mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+ mRegistry2.handleLifecycleEvent(Lifecycle.Event.ON_START);
+
+ verify(observer1).onChanged("1");
+ verify(observer1).onChanged("1");
+ }
+
+ @Test
+ public void setValue_twoObserversOneStartedAfterSetValue_onChangedCalledOnCorrectObserver() {
+ Observer<String> observer1 = mock(Observer.class);
+ Observer<String> observer2 = mock(Observer.class);
+
+ mLiveData.observe(mOwner, observer1);
+ mLiveData.observe(mOwner2, observer2);
+
+ mLiveData.setValue("1");
+
+ mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+
+ verify(observer1).onChanged("1");
+ verify(observer2, never()).onChanged(anyString());
+ }
+
+ @Test
+ public void setValue_twoObserversOneStarted_liveDataBecomesActive() {
+ Observer<String> observer1 = mock(Observer.class);
+ Observer<String> observer2 = mock(Observer.class);
+
+ mLiveData.observe(mOwner, observer1);
+ mLiveData.observe(mOwner2, observer2);
+
+ mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+
+ verify(mActiveObserversChanged).onCall(true);
+ }
+
+ @Test
+ public void setValue_twoObserversOneStopped_liveDataStaysActive() {
+ Observer<String> observer1 = mock(Observer.class);
+ Observer<String> observer2 = mock(Observer.class);
+
+ mLiveData.observe(mOwner, observer1);
+ mLiveData.observe(mOwner2, observer2);
+
+ mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+ mRegistry2.handleLifecycleEvent(Lifecycle.Event.ON_START);
+
+ verify(mActiveObserversChanged).onCall(true);
+
+ reset(mActiveObserversChanged);
+ mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
+
+ verify(mActiveObserversChanged, never()).onCall(anyBoolean());
+ }
+
+ /**
+ * Verifies that if a lifecycle's state changes without an event, and changes to something that
+ * LiveData would become inactive in response to, LiveData will detect the change upon new data
+ * being set and become inactive. Also verifies that once the lifecycle enters into a state
+ * that LiveData should become active to, that it does indeed become active.
+ */
+ @Test
+ public void liveDataActiveStateIsManagedCorrectlyWithoutEvent_oneObserver() {
+ Observer<String> observer = (Observer<String>) mock(Observer.class);
+ mLiveData.observe(mOwner, observer);
+
+ mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+
+ // Marking state as CREATED should call onInactive.
+ reset(mActiveObserversChanged);
+ mRegistry.markState(Lifecycle.State.CREATED);
+ 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");
+
+ // state is already CREATED so should not call again
+ verify(mActiveObserversChanged, never()).onCall(anyBoolean());
+ verify(observer, never()).onChanged(anyString());
+
+ // 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");
+ }
+
+ /**
+ * This test verifies that LiveData will detect changes in LifecycleState that would make it
+ * inactive upon the setting of new data, but only if all of the Lifecycles it's observing
+ * are all in those states. It also makes sure that once it is inactive, that it will become
+ * active again once one of the lifecycles it's observing moves to an appropriate state.
+ */
+ @Test
+ public void liveDataActiveStateIsManagedCorrectlyWithoutEvent_twoObservers() {
+ Observer<String> observer1 = mock(Observer.class);
+ Observer<String> observer2 = mock(Observer.class);
+
+ mLiveData.observe(mOwner, observer1);
+ mLiveData.observe(mOwner2, observer2);
+
+ mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+ mRegistry2.handleLifecycleEvent(Lifecycle.Event.ON_START);
+
+ // Marking the state to created won't change LiveData to be inactive.
+ reset(mActiveObserversChanged);
+ mRegistry.markState(Lifecycle.State.CREATED);
+ verify(mActiveObserversChanged, never()).onCall(anyBoolean());
+
+ // After setting a value, the LiveData will stay active because there is still a STARTED
+ // lifecycle being observed. The one Observer associated with the STARTED lifecycle will
+ // also have been called, but the other Observer will not have been called.
+ reset(observer1);
+ reset(observer2);
+ mLiveData.setValue("1");
+ verify(mActiveObserversChanged, never()).onCall(anyBoolean());
+ verify(observer1, never()).onChanged(anyString());
+ verify(observer2).onChanged("1");
+
+ // 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).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, live data
+ // will not dispatch any values
+ reset(mActiveObserversChanged);
+ mLiveData.setValue("2");
+ verify(mActiveObserversChanged, never()).onCall(anyBoolean());
+ verify(observer1, never()).onChanged(anyString());
+ verify(observer2, never()).onChanged(anyString());
+
+ // Now that the first Lifecycle has been moved back to the Resumed state, the LiveData will
+ // 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");
+ verify(observer2, never()).onChanged(anyString());
+ }
+
@SuppressWarnings("WeakerAccess")
static class PublicLiveData<T> extends LiveData<T> {
// cannot spy due to internal calls
diff --git a/android/arch/lifecycle/MediatorLiveData.java b/android/arch/lifecycle/MediatorLiveData.java
index 672b3a3b..58647394 100644
--- a/android/arch/lifecycle/MediatorLiveData.java
+++ b/android/arch/lifecycle/MediatorLiveData.java
@@ -19,16 +19,49 @@ package android.arch.lifecycle;
import android.arch.core.internal.SafeIterableMap;
import android.support.annotation.CallSuper;
import android.support.annotation.MainThread;
+import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.util.Map;
/**
- * {@link LiveData} subclass which may observer other {@code LiveData} objects and react on
+ * {@link LiveData} subclass which may observe other {@code LiveData} objects and react on
* {@code OnChanged} events from them.
* <p>
* This class correctly propagates its active/inactive states down to source {@code LiveData}
* objects.
+ * <p>
+ * Consider the following scenario: we have 2 instances of {@code LiveData}, let's name them
+ * {@code liveData1} and {@code liveData2}, and we want to merge their emissions in one object:
+ * {@code liveDataMerger}. Then, {@code liveData1} and {@code liveData2} will become sources for
+ * the {@code MediatorLiveData liveDataMerger} and every time {@code onChanged} callback
+ * is called for either of them, we set a new value in {@code liveDataMerger}.
+ *
+ * <pre>
+ * LiveData<Integer> liveData1 = ...;
+ * LiveData<Integer> liveData2 = ...;
+ *
+ * MediatorLiveData<Integer> liveDataMerger = new MediatorLiveData<>();
+ * liveDataMerger.addSource(liveData1, value -> liveDataMerger.setValue(value));
+ * liveDataMerger.addSource(liveData2, value -> liveDataMerger.setValue(value));
+ * </pre>
+ * <p>
+ * Let's consider that we only want 10 values emitted by {@code liveData1}, to be
+ * merged in the {@code liveDataMerger}. Then, after 10 values, we can stop listening to {@code
+ * liveData1} and remove it as a source.
+ * <pre>
+ * liveDataMerger.addSource(liveData1, new Observer<Integer>() {
+ * private int count = 1;
+ *
+ * {@literal @}Override public void onChanged(@Nullable Integer s) {
+ * count++;
+ * liveDataMerger.setValue(s);
+ * if (count > 10) {
+ * liveDataMerger.removeSource(liveData1);
+ * }
+ * }
+ * });
+ * </pre>
*
* @param <T> The type of data hold by this instance
*/
@@ -49,7 +82,7 @@ public class MediatorLiveData<T> extends MutableLiveData<T> {
* @param <S> The type of data hold by {@code source} LiveData
*/
@MainThread
- public <S> void addSource(LiveData<S> source, Observer<S> onChanged) {
+ public <S> void addSource(@NonNull LiveData<S> source, @NonNull Observer<S> onChanged) {
Source<S> e = new Source<>(source, onChanged);
Source<?> existing = mSources.putIfAbsent(source, e);
if (existing != null && existing.mObserver != onChanged) {
@@ -71,7 +104,7 @@ public class MediatorLiveData<T> extends MutableLiveData<T> {
* @param <S> the type of data hold by {@code source} LiveData
*/
@MainThread
- public <S> void removeSource(LiveData<S> toRemote) {
+ public <S> void removeSource(@NonNull LiveData<S> toRemote) {
Source<?> source = mSources.remove(toRemote);
if (source != null) {
source.unplug();
diff --git a/android/arch/lifecycle/MissingClassTest.java b/android/arch/lifecycle/MissingClassTest.java
new file mode 100644
index 00000000..81a07564
--- /dev/null
+++ b/android/arch/lifecycle/MissingClassTest.java
@@ -0,0 +1,44 @@
+/*
+ * 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 android.app.PictureInPictureParams;
+import android.os.Build;
+import android.support.test.filters.SdkSuppress;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SdkSuppress(maxSdkVersion = Build.VERSION_CODES.N_MR1)
+@SmallTest
+public class MissingClassTest {
+ public static class ObserverWithMissingClasses {
+ @SuppressWarnings("unused")
+ public void newApiMethod(PictureInPictureParams params) {}
+
+ @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
+ public void onResume() {}
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testMissingApi() {
+ new ReflectiveGenericLifecycleObserver(new ObserverWithMissingClasses());
+ }
+}
diff --git a/android/arch/lifecycle/PartiallyCoveredActivityTest.java b/android/arch/lifecycle/PartiallyCoveredActivityTest.java
new file mode 100644
index 00000000..07a9dc5a
--- /dev/null
+++ b/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/android/arch/lifecycle/ProcessLifecycleOwner.java b/android/arch/lifecycle/ProcessLifecycleOwner.java
index e2a12563..179e2c47 100644
--- a/android/arch/lifecycle/ProcessLifecycleOwner.java
+++ b/android/arch/lifecycle/ProcessLifecycleOwner.java
@@ -22,6 +22,7 @@ import android.arch.lifecycle.ReportFragment.ActivityInitializationListener;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
+import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
/**
@@ -156,7 +157,8 @@ public class ProcessLifecycleOwner implements LifecycleOwner {
app.registerActivityLifecycleCallbacks(new EmptyActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
- ReportFragment .get(activity).setProcessListener(mInitializationListener);
+ ReportFragment.injectIfNeededIn(activity);
+ ReportFragment.get(activity).setProcessListener(mInitializationListener);
}
@Override
@@ -171,6 +173,7 @@ public class ProcessLifecycleOwner implements LifecycleOwner {
});
}
+ @NonNull
@Override
public Lifecycle getLifecycle() {
return mRegistry;
diff --git a/android/arch/lifecycle/LifecycleRuntimeTrojanProvider.java b/android/arch/lifecycle/ProcessLifecycleOwnerInitializer.java
index ac278c0c..8ba297fe 100644
--- a/android/arch/lifecycle/LifecycleRuntimeTrojanProvider.java
+++ b/android/arch/lifecycle/ProcessLifecycleOwnerInitializer.java
@@ -29,10 +29,9 @@ import android.support.annotation.RestrictTo;
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class LifecycleRuntimeTrojanProvider extends ContentProvider {
+public class ProcessLifecycleOwnerInitializer extends ContentProvider {
@Override
public boolean onCreate() {
- LifecycleDispatcher.init(getContext());
ProcessLifecycleOwner.init(getContext());
return true;
}
diff --git a/android/arch/lifecycle/ProcessOwnerTest.java b/android/arch/lifecycle/ProcessOwnerTest.java
index 37bdcdb4..77baf94c 100644
--- a/android/arch/lifecycle/ProcessOwnerTest.java
+++ b/android/arch/lifecycle/ProcessOwnerTest.java
@@ -31,6 +31,7 @@ import android.arch.lifecycle.Lifecycle.Event;
import android.arch.lifecycle.testapp.NavigationDialogActivity;
import android.arch.lifecycle.testapp.NavigationTestActivityFirst;
import android.arch.lifecycle.testapp.NavigationTestActivitySecond;
+import android.arch.lifecycle.testapp.NonSupportActivity;
import android.content.Context;
import android.content.Intent;
import android.support.test.InstrumentationRegistry;
@@ -95,6 +96,22 @@ public class ProcessOwnerTest {
}
@Test
+ public void testNavigationToNonSupport() throws Throwable {
+ FragmentActivity firstActivity = setupObserverOnResume();
+ Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor(
+ NonSupportActivity.class.getCanonicalName(), null, false);
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ instrumentation.addMonitor(monitor);
+
+ Intent intent = new Intent(firstActivity, NonSupportActivity.class);
+ firstActivity.finish();
+ firstActivity.startActivity(intent);
+ NonSupportActivity secondActivity = (NonSupportActivity) monitor.waitForActivity();
+ assertThat("Failed to navigate", secondActivity, notNullValue());
+ checkProcessObserverSilent(secondActivity);
+ }
+
+ @Test
public void testRecreation() throws Throwable {
FragmentActivity activity = setupObserverOnResume();
FragmentActivity recreated = TestUtils.recreateActivity(activity, activityTestRule);
@@ -164,4 +181,11 @@ public class ProcessOwnerTest {
activityTestRule.runOnUiThread(() ->
ProcessLifecycleOwner.get().getLifecycle().removeObserver(mObserver));
}
+
+ private void checkProcessObserverSilent(NonSupportActivity activity) throws Throwable {
+ assertThat(activity.awaitResumedState(), is(true));
+ assertThat(mObserver.mChangedState, is(false));
+ activityTestRule.runOnUiThread(() ->
+ ProcessLifecycleOwner.get().getLifecycle().removeObserver(mObserver));
+ }
}
diff --git a/android/arch/lifecycle/ReportFragment.java b/android/arch/lifecycle/ReportFragment.java
index 3e4ece82..16a89ce8 100644
--- a/android/arch/lifecycle/ReportFragment.java
+++ b/android/arch/lifecycle/ReportFragment.java
@@ -28,7 +28,6 @@ import android.support.annotation.RestrictTo;
*/
@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/android/arch/lifecycle/TestUtils.java b/android/arch/lifecycle/TestUtils.java
index f0214bfb..f7f9bbe5 100644
--- a/android/arch/lifecycle/TestUtils.java
+++ b/android/arch/lifecycle/TestUtils.java
@@ -16,16 +16,35 @@
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.app.FragmentActivity;
+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;
class TestUtils {
@@ -61,23 +80,88 @@ class TestUtils {
return result;
}
- static void waitTillResumed(final FragmentActivity a, ActivityTestRule<?> activityRule)
+ static void waitTillCreated(final LifecycleOwner owner, ActivityTestRule<?> activityRule)
+ throws Throwable {
+ waitTillState(owner, activityRule, CREATED);
+ }
+
+ static void waitTillStarted(final LifecycleOwner owner, ActivityTestRule<?> activityRule)
+ throws Throwable {
+ waitTillState(owner, activityRule, STARTED);
+ }
+
+ 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(() -> {
- Lifecycle.State currentState = a.getLifecycle().getCurrentState();
- if (currentState == RESUMED) {
+ Lifecycle.State currentState = owner.getLifecycle().getCurrentState();
+ if (currentState == state) {
latch.countDown();
+ } else {
+ owner.getLifecycle().addObserver(new LifecycleObserver() {
+ @OnLifecycleEvent(Lifecycle.Event.ON_ANY)
+ public void onStateChanged(LifecycleOwner provider) {
+ if (provider.getLifecycle().getCurrentState() == state) {
+ latch.countDown();
+ provider.getLifecycle().removeObserver(this);
+ }
+ }
+ });
}
- a.getLifecycle().addObserver(new LifecycleObserver() {
- @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
- public void onStateChanged(LifecycleOwner provider) {
- latch.countDown();
- provider.getLifecycle().removeObserver(this);
- }
- });
});
- latch.await();
+ 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/android/arch/lifecycle/Transformations.java b/android/arch/lifecycle/Transformations.java
index 9ce9cbb7..c735f8ba 100644
--- a/android/arch/lifecycle/Transformations.java
+++ b/android/arch/lifecycle/Transformations.java
@@ -18,6 +18,7 @@ package android.arch.lifecycle;
import android.arch.core.util.Function;
import android.support.annotation.MainThread;
+import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
/**
@@ -60,7 +61,8 @@ public class Transformations {
* @return a LiveData which emits resulting values
*/
@MainThread
- public static <X, Y> LiveData<Y> map(LiveData<X> source, final Function<X, Y> func) {
+ public static <X, Y> LiveData<Y> map(@NonNull LiveData<X> source,
+ @NonNull final Function<X, Y> func) {
final MediatorLiveData<Y> result = new MediatorLiveData<>();
result.addSource(source, new Observer<X>() {
@Override
@@ -120,8 +122,8 @@ public class Transformations {
* @param <Y> a type of resulting LiveData
*/
@MainThread
- public static <X, Y> LiveData<Y> switchMap(LiveData<X> trigger,
- final Function<X, LiveData<Y>> func) {
+ public static <X, Y> LiveData<Y> switchMap(@NonNull LiveData<X> trigger,
+ @NonNull final Function<X, LiveData<Y>> func) {
final MediatorLiveData<Y> result = new MediatorLiveData<>();
result.addSource(trigger, new Observer<X>() {
LiveData<Y> mSource;
diff --git a/android/arch/lifecycle/ViewModelProvider.java b/android/arch/lifecycle/ViewModelProvider.java
index 7ef591f3..29cbab8e 100644
--- a/android/arch/lifecycle/ViewModelProvider.java
+++ b/android/arch/lifecycle/ViewModelProvider.java
@@ -43,7 +43,8 @@ public class ViewModelProvider {
* @param <T> The type parameter for the ViewModel.
* @return a newly created ViewModel
*/
- <T extends ViewModel> T create(Class<T> modelClass);
+ @NonNull
+ <T extends ViewModel> T create(@NonNull Class<T> modelClass);
}
private final Factory mFactory;
@@ -70,7 +71,7 @@ public class ViewModelProvider {
* @param factory factory a {@code Factory} which will be used to instantiate
* new {@code ViewModels}
*/
- public ViewModelProvider(ViewModelStore store, Factory factory) {
+ public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
this.mViewModelStore = store;
}
@@ -88,7 +89,8 @@ public class ViewModelProvider {
* @param <T> The type parameter for the ViewModel.
* @return A ViewModel that is an instance of the given type {@code T}.
*/
- public <T extends ViewModel> T get(Class<T> modelClass) {
+ @NonNull
+ public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
String canonicalName = modelClass.getCanonicalName();
if (canonicalName == null) {
throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
@@ -136,8 +138,9 @@ public class ViewModelProvider {
*/
public static class NewInstanceFactory implements Factory {
+ @NonNull
@Override
- public <T extends ViewModel> T create(Class<T> modelClass) {
+ public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
//noinspection TryWithIdenticalCatches
try {
return modelClass.newInstance();
diff --git a/android/arch/lifecycle/ViewModelProviders.java b/android/arch/lifecycle/ViewModelProviders.java
index 746162a9..b4b20aa4 100644
--- a/android/arch/lifecycle/ViewModelProviders.java
+++ b/android/arch/lifecycle/ViewModelProviders.java
@@ -139,8 +139,9 @@ public class ViewModelProviders {
mApplication = application;
}
+ @NonNull
@Override
- public <T extends ViewModel> T create(Class<T> modelClass) {
+ public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
//noinspection TryWithIdenticalCatches
try {
diff --git a/android/arch/lifecycle/ViewModelStoreOwner.java b/android/arch/lifecycle/ViewModelStoreOwner.java
index 50583056..e26fa325 100644
--- a/android/arch/lifecycle/ViewModelStoreOwner.java
+++ b/android/arch/lifecycle/ViewModelStoreOwner.java
@@ -16,6 +16,8 @@
package android.arch.lifecycle;
+import android.support.annotation.NonNull;
+
/**
* A scope that owns {@link ViewModelStore}.
* <p>
@@ -30,5 +32,6 @@ public interface ViewModelStoreOwner {
*
* @return a {@code ViewModelStore}
*/
+ @NonNull
ViewModelStore getViewModelStore();
}
diff --git a/android/arch/lifecycle/ViewModelStores.java b/android/arch/lifecycle/ViewModelStores.java
index 8c17dd98..d7d769d6 100644
--- a/android/arch/lifecycle/ViewModelStores.java
+++ b/android/arch/lifecycle/ViewModelStores.java
@@ -19,6 +19,7 @@ package android.arch.lifecycle;
import static android.arch.lifecycle.HolderFragment.holderFragmentFor;
import android.support.annotation.MainThread;
+import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
@@ -38,7 +39,7 @@ public class ViewModelStores {
* @return a {@code ViewModelStore}
*/
@MainThread
- public static ViewModelStore of(FragmentActivity activity) {
+ public static ViewModelStore of(@NonNull FragmentActivity activity) {
return holderFragmentFor(activity).getViewModelStore();
}
@@ -49,7 +50,7 @@ public class ViewModelStores {
* @return a {@code ViewModelStore}
*/
@MainThread
- public static ViewModelStore of(Fragment fragment) {
+ public static ViewModelStore of(@NonNull Fragment fragment) {
return holderFragmentFor(fragment).getViewModelStore();
}
}
diff --git a/android/arch/lifecycle/testapp/CollectingActivity.java b/android/arch/lifecycle/testapp/CollectingLifecycleOwner.java
index 6e243b6c..4213cab9 100644
--- a/android/arch/lifecycle/testapp/CollectingActivity.java
+++ b/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/android/arch/lifecycle/testapp/CollectingSupportActivity.java b/android/arch/lifecycle/testapp/CollectingSupportActivity.java
new file mode 100644
index 00000000..f38d4224
--- /dev/null
+++ b/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/android/arch/lifecycle/testapp/CollectingSupportFragment.java b/android/arch/lifecycle/testapp/CollectingSupportFragment.java
new file mode 100644
index 00000000..9bbbe165
--- /dev/null
+++ b/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/android/arch/lifecycle/testapp/FrameworkLifecycleRegistryActivity.java b/android/arch/lifecycle/testapp/FrameworkLifecycleRegistryActivity.java
index d8f4fb39..cdf577c1 100644
--- a/android/arch/lifecycle/testapp/FrameworkLifecycleRegistryActivity.java
+++ b/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 @@ public class FrameworkLifecycleRegistryActivity extends Activity implements
@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/android/arch/lifecycle/testapp/FullLifecycleTestActivity.java b/android/arch/lifecycle/testapp/FullLifecycleTestActivity.java
deleted file mode 100644
index 5f33c282..00000000
--- a/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/android/arch/lifecycle/testapp/MainActivity.java b/android/arch/lifecycle/testapp/MainActivity.java
deleted file mode 100644
index b9d59142..00000000
--- a/android/arch/lifecycle/testapp/MainActivity.java
+++ /dev/null
@@ -1,31 +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 android.os.Bundle;
-import android.support.v4.app.FragmentActivity;
-
-/**
- * Simple test activity
- */
-public class MainActivity extends FragmentActivity {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity);
- }
-}
diff --git a/android/arch/lifecycle/testapp/NavigationDialogActivity.java b/android/arch/lifecycle/testapp/NavigationDialogActivity.java
index 0ae94033..7d53528f 100644
--- a/android/arch/lifecycle/testapp/NavigationDialogActivity.java
+++ b/android/arch/lifecycle/testapp/NavigationDialogActivity.java
@@ -22,4 +22,10 @@ import android.support.v4.app.FragmentActivity;
* 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/android/arch/lifecycle/testapp/NonSupportActivity.java b/android/arch/lifecycle/testapp/NonSupportActivity.java
new file mode 100644
index 00000000..835d846a
--- /dev/null
+++ b/android/arch/lifecycle/testapp/NonSupportActivity.java
@@ -0,0 +1,85 @@
+/*
+ * 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.app.Activity;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Activity which doesn't extend FragmentActivity, to test ProcessLifecycleOwner because it
+ * should work anyway.
+ */
+public class NonSupportActivity extends Activity {
+
+ private static final int TIMEOUT = 1; //secs
+ private final Lock mLock = new ReentrantLock();
+ private Condition mIsResumedCondition = mLock.newCondition();
+ private boolean mIsResumed = false;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mLock.lock();
+ try {
+ mIsResumed = true;
+ mIsResumedCondition.signalAll();
+ } finally {
+ mLock.unlock();
+ }
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ mLock.lock();
+ try {
+ mIsResumed = false;
+ } finally {
+ mLock.unlock();
+ }
+ }
+
+ /**
+ * awaits resumed state
+ * @return
+ * @throws InterruptedException
+ */
+ public boolean awaitResumedState() throws InterruptedException {
+ mLock.lock();
+ try {
+ while (!mIsResumed) {
+ if (!mIsResumedCondition.await(TIMEOUT, TimeUnit.SECONDS)) {
+ return false;
+ }
+ }
+ return true;
+ } finally {
+ mLock.unlock();
+ }
+ }
+}
diff --git a/android/arch/lifecycle/testapp/SupportLifecycleRegistryActivity.java b/android/arch/lifecycle/testapp/SupportLifecycleRegistryActivity.java
deleted file mode 100644
index c46c6d3e..00000000
--- a/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/android/arch/lifecycle/testapp/TestEvent.java b/android/arch/lifecycle/testapp/TestEvent.java
index 0929f84a..788045a2 100644
--- a/android/arch/lifecycle/testapp/TestEvent.java
+++ b/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/android/arch/lifecycle/testapp/TestObserver.java b/android/arch/lifecycle/testapp/TestObserver.java
index c6112396..00b8e16d 100644
--- a/android/arch/lifecycle/testapp/TestObserver.java
+++ b/android/arch/lifecycle/testapp/TestObserver.java
@@ -28,7 +28,7 @@ import android.arch.lifecycle.Lifecycle.Event;
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;