LifecyleRegistry

This CL adds the helper class so that Fragments and Activities can
implement LifecycleProvider.

Test: com.android.support.lifecycle.LifecycleRegistryTest\
      com.android.support.lifecycle.ObserverListTest

Bug: 32342385
Change-Id: Ie3402969b06760cb277cff0b176ffff2dd7d6e2a
diff --git a/lifecycle/common/src/main/java/com/android/support/lifecycle/Lifecycle.java b/lifecycle/common/src/main/java/com/android/support/lifecycle/Lifecycle.java
index 9ff11ef..7322bff 100644
--- a/lifecycle/common/src/main/java/com/android/support/lifecycle/Lifecycle.java
+++ b/lifecycle/common/src/main/java/com/android/support/lifecycle/Lifecycle.java
@@ -12,7 +12,6 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
- *
  */
 
 package com.android.support.lifecycle;
@@ -22,12 +21,37 @@
 
 @SuppressWarnings({"UnnecessaryInterfaceModifier", "WeakerAccess", "unused"})
 public interface Lifecycle {
+    /**
+     * Adds a LifecycleObserver that will be notified when the owner LifecycleProvide changes state.
+     * <p>
+     * If this method is called while a state change is being dispatched, the given observer will
+     * not receive that event.
+     *
+     * @param observer The observer to notify.
+     */
     @MainThread
-    void addObserver(LifecycleObserver callback);
+    void addObserver(LifecycleObserver observer);
+
+    /**
+     * Removes the given observer from the observers list.
+     * <p>
+     * If this method is called while a state change is being dispatched,
+     *  * If the given observer has not yet received that event, it will not receive it.
+     *  * If the given observer has more than 1 methods that observes the currently dispatched event
+     *  and at least one of them received the event, all of them will receive the event and the
+     *  removal will happen afterwards.
+     *
+     * @param observer The observer to be removed.
+     */
     @MainThread
-    void removeObserver(LifecycleObserver removeCallback);
-    @MainThread
-    @State
+    void removeObserver(LifecycleObserver observer);
+
+    /**
+     * Returns the current state of the Lifecycle.
+     *
+     * @return The current state of the Lifecycle.
+     */
+    @MainThread @State
     public int getCurrentState();
     public static final int INITIALIZED = 1;
     public static final int CREATED = INITIALIZED << 2;
@@ -39,7 +63,8 @@
     public static final int FINISHED = DESTROYED << 2;
     public static final int ANY = -1;
 
-    @IntDef(value = {INITIALIZED, CREATED, STARTED, RESUMED, PAUSED, STOPPED, DESTROYED, ANY}, flag = true)
+    @IntDef(value = {INITIALIZED, CREATED, STARTED, RESUMED, PAUSED, STOPPED, DESTROYED, ANY},
+            flag = true)
     public @interface State {
     }
 }
diff --git a/lifecycle/common/src/main/java/com/android/support/lifecycle/ReflectiveGenericLifecycleObserver.java b/lifecycle/common/src/main/java/com/android/support/lifecycle/ReflectiveGenericLifecycleObserver.java
index 8286981..5d1ea3a 100644
--- a/lifecycle/common/src/main/java/com/android/support/lifecycle/ReflectiveGenericLifecycleObserver.java
+++ b/lifecycle/common/src/main/java/com/android/support/lifecycle/ReflectiveGenericLifecycleObserver.java
@@ -46,6 +46,7 @@
         invokeCallbacks(mInfo, source, state, previousState);
     }
 
+    @SuppressWarnings("ConstantConditions")
     private void invokeCallbacks(CallbackInfo info, LifecycleProvider source,
             @Lifecycle.State int state, @Lifecycle.State int previousState) {
         if ((info.mStates & state) != 0) {
@@ -54,7 +55,7 @@
                 invokeCallback(reference, source, previousState, state);
             }
         }
-        // TODO prevent duplicate calls into the same method. Preferebly while parsing
+        // TODO prevent duplicate calls into the same method. Preferably while parsing
         if (info.mSuper != null) {
             invokeCallbacks(info.mSuper, source, state, previousState);
         }
diff --git a/lifecycle/compiler/build.gradle b/lifecycle/compiler/build.gradle
index 808296f..335cdad 100644
--- a/lifecycle/compiler/build.gradle
+++ b/lifecycle/compiler/build.gradle
@@ -17,6 +17,7 @@
 
 apply plugin: 'kotlin'
 apply plugin: 'maven'
+apply plugin: 'checkstyle'
 
 sourceSets {
     test.java.srcDirs += 'src/tests/kotlin'
@@ -38,4 +39,13 @@
             pom.artifactId = 'lifecycle-compiler'
         }
     }
+}
+
+task('checkstyle', type: Checkstyle) {
+    configFile rootProject.file('kotlin-style.xml')
+    source 'src/main/kotlin'
+    ignoreFailures false
+    showViolations true
+    include '**/*.kt'
+    classpath = files()
 }
\ No newline at end of file
diff --git a/lifecycle/compiler/src/main/kotlin/com/android/support/lifecycle/LifecycleProcessor.kt b/lifecycle/compiler/src/main/kotlin/com/android/support/lifecycle/LifecycleProcessor.kt
index 8497dc0..fa6d0a6 100644
--- a/lifecycle/compiler/src/main/kotlin/com/android/support/lifecycle/LifecycleProcessor.kt
+++ b/lifecycle/compiler/src/main/kotlin/com/android/support/lifecycle/LifecycleProcessor.kt
@@ -12,7 +12,6 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
- *
  */
 
 package com.android.support.lifecycle
@@ -264,4 +263,4 @@
     data class StateMethod(val method: ExecutableElement, val onState: OnState)
 
     data class LifecycleObserverInfo(val type: TypeElement, val methods: List<StateMethod>)
-}
\ No newline at end of file
+}
diff --git a/lifecycle/extensions/.gitignore b/lifecycle/extensions/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/lifecycle/extensions/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/lifecycle/extensions/build.gradle b/lifecycle/extensions/build.gradle
new file mode 100644
index 0000000..7b32435
--- /dev/null
+++ b/lifecycle/extensions/build.gradle
@@ -0,0 +1,27 @@
+apply plugin: 'com.android.library'
+
+android {
+    compileSdkVersion 24
+    buildToolsVersion "25.0.0"
+
+    defaultConfig {
+        minSdkVersion 14
+        targetSdkVersion 24
+        versionCode 1
+        versionName "1.0"
+
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+}
+
+dependencies {
+    compile project(":runtime")
+    testCompile 'junit:junit:4.12'
+}
diff --git a/lifecycle/extensions/proguard-rules.pro b/lifecycle/extensions/proguard-rules.pro
new file mode 100644
index 0000000..b7210d1
--- /dev/null
+++ b/lifecycle/extensions/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /Users/yboyar/android/sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
diff --git a/lifecycle/extensions/src/main/AndroidManifest.xml b/lifecycle/extensions/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..1ed5b75
--- /dev/null
+++ b/lifecycle/extensions/src/main/AndroidManifest.xml
@@ -0,0 +1,3 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.support.lifecycle.ext">
+</manifest>
diff --git a/lifecycle/kotlin-checkstyle.xml b/lifecycle/kotlin-checkstyle.xml
new file mode 100644
index 0000000..5cdad0a6
--- /dev/null
+++ b/lifecycle/kotlin-checkstyle.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright (C) 2016 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<!DOCTYPE module PUBLIC "-//Puppy Crawl//DTD Check Configuration 1.3//EN"
+    "http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
+<!-- this is a very limited set of checkstyle rules mainly because TreeWalker rules are not
+supported for kt files yet.-->
+<module name="Checker">
+    <property name="severity" value="warning"/>
+    <property name="charset" value="UTF-8"/>
+    <module name="SuppressionCommentFilter">
+        <property name="offCommentFormat" value="CHECKSTYLE:OFF IndentationCheck"/>
+        <property name="onCommentFormat" value="CHECKSTYLE:ON IndentationCheck"/>
+        <property name="checkFormat" value="IndentationCheck"/>
+    </module>
+    <module name="SuppressionCommentFilter">
+        <property name="offCommentFormat" value="CHECKSTYLE:OFF Generated code"/>
+        <property name="onCommentFormat" value="CHECKSTYLE:ON Generated code"/>
+    </module>
+    <module name="FileTabCharacter">
+        <property name="severity" value="error"/>
+    </module>
+    <module name="NewlineAtEndOfFile">
+        <property name="severity" value="error"/>
+    </module>
+    <module name="RegexpSingleline">
+        <property name="severity" value="error"/>
+        <property name="format" value="[ \t]+$"/>
+        <property name="message" value="Trailing whitespace"/>
+    </module>
+    <module name="RegexpHeader">
+        <property name="severity" value="error"/>
+        <message key="header.mismatch"
+                 value="Android Copyright header seems to be incorrect. Expected ''{0}'' on this line."/>
+        <property name="header" value="^/\*\n \* Copyright \([Cc]\) [0-9]{4} The Android Open Source Project\n \*\n \* Licensed under the Apache License, Version 2\.0 \(the \&quot;License\&quot;\);\n \* you may not use this file except in compliance with the License.\n \* You may obtain a copy of the License at\n \*\n \*      https:\/\/ptop.only.wip.la:443\/http\/www\.apache\.org\/licenses\/LICENSE-2\.0\n \*\n \* Unless required by applicable law or agreed to in writing, software\n \* distributed under the License is distributed on an &quot;AS IS&quot; BASIS,\n \* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied\.\n \* See the License for the specific language governing permissions and\n \* limitations under the License\.\n \*\/" />
+    </module>
+</module>
diff --git a/lifecycle/runtime/.gitignore b/lifecycle/runtime/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/lifecycle/runtime/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/lifecycle/runtime/build.gradle b/lifecycle/runtime/build.gradle
new file mode 100644
index 0000000..8e6883a
--- /dev/null
+++ b/lifecycle/runtime/build.gradle
@@ -0,0 +1,34 @@
+apply plugin: 'com.android.library'
+
+android {
+    compileSdkVersion 25
+    buildToolsVersion "25.0.0"
+
+    defaultConfig {
+        minSdkVersion 14
+        targetSdkVersion 25
+        versionCode 1
+        versionName "1.0"
+
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+
+    testOptions {
+        unitTests.returnDefaultValues = true
+    }
+}
+
+dependencies {
+    compile project(":common")
+    // using classes like Pair from support core allows Lifecycle to be compatible w/ junit tests
+    compile "com.android.support:support-core-utils:25.0.0"
+    testCompile 'junit:junit:4.12'
+    testCompile "org.mockito:mockito-core:1.9.5"
+}
diff --git a/lifecycle/runtime/proguard-rules.pro b/lifecycle/runtime/proguard-rules.pro
new file mode 100644
index 0000000..b7210d1
--- /dev/null
+++ b/lifecycle/runtime/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /Users/yboyar/android/sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
diff --git a/lifecycle/runtime/src/main/AndroidManifest.xml b/lifecycle/runtime/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..7e2ad1f
--- /dev/null
+++ b/lifecycle/runtime/src/main/AndroidManifest.xml
@@ -0,0 +1,3 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.support.lifecycle">
+</manifest>
diff --git a/lifecycle/runtime/src/main/java/com/android/support/lifecycle/LifecycleRegistry.java b/lifecycle/runtime/src/main/java/com/android/support/lifecycle/LifecycleRegistry.java
new file mode 100644
index 0000000..164d17c
--- /dev/null
+++ b/lifecycle/runtime/src/main/java/com/android/support/lifecycle/LifecycleRegistry.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.support.lifecycle;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.VisibleForTesting;
+
+/**
+ * An implementation of {@link Lifecycle} that can handle multiple observers.
+ * <p>
+ * It is used by Fragments and Support Library Activities. You can also directly use it if you have
+ * a custom LifecycleProvider.
+ */
+@SuppressWarnings("WeakerAccess")
+public class LifecycleRegistry implements Lifecycle {
+    static final String TAG = "LifecycleRegistry";
+    /**
+     * Custom list that keeps observers and can handle removals / additions during traversal.
+     */
+    private ObserverList mObserverList = new ObserverList();
+
+    /**
+     * Latest state that was provided via {@link #setCurrentState(int)}.
+     */
+    @State
+    private int mCurrentState;
+
+    /**
+     * Previously set state. This allows us to re-use the dispatcher while traversing listeners.
+     */
+    @VisibleForTesting
+    private int mPrevState;
+
+    /**
+     * The provider that owns this Lifecycle.
+     */
+    private final LifecycleProvider mLifecycleProvider;
+
+    private final ObserverList.Callback mDispatchCallback = new ObserverList.Callback() {
+        @Override
+        public void run(GenericLifecycleObserver observer) {
+            observer.onStateChanged(mLifecycleProvider, mPrevState);
+        }
+    };
+
+    /**
+     * Creates a new LifecycleRegistry for the given provider.
+     * <p>
+     * You should usually create this inside your LifecycleProvider class's constructor and hold
+     * onto the same instance.
+     *
+     * @param provider     The owner LifecycleProvider
+     * @param initialState The start state.
+     */
+    public LifecycleRegistry(@NonNull LifecycleProvider provider, @State int initialState) {
+        mCurrentState = initialState;
+        mLifecycleProvider = provider;
+    }
+
+    /**
+     * Sets the current state and notifies the observers.
+     * <p>
+     * Note that if the {@code currentState} is the same state as the last call to this method,
+     * calling this method has no effect.
+     *
+     * @param currentState The updated state of the LifecycleProvider.
+     */
+    public void setCurrentState(@State int currentState) {
+        if (mCurrentState == currentState) {
+            return;
+        }
+        mPrevState = mCurrentState;
+        mCurrentState = currentState;
+        mObserverList.forEach(mDispatchCallback);
+    }
+
+    @Override
+    public void addObserver(LifecycleObserver observer) {
+        mObserverList.add(observer);
+    }
+
+    @Override
+    public void removeObserver(LifecycleObserver observer) {
+        mObserverList.remove(observer);
+    }
+
+    /**
+     * The number of observers.
+     *
+     * @return The number of observers.
+     */
+    public int size() {
+        return mObserverList.size();
+    }
+
+    @Override
+    @State
+    public int getCurrentState() {
+        return mCurrentState;
+    }
+}
diff --git a/lifecycle/runtime/src/main/java/com/android/support/lifecycle/ObserverList.java b/lifecycle/runtime/src/main/java/com/android/support/lifecycle/ObserverList.java
new file mode 100644
index 0000000..e1e8d5a
--- /dev/null
+++ b/lifecycle/runtime/src/main/java/com/android/support/lifecycle/ObserverList.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.support.lifecycle;
+
+import android.support.v4.util.Pair;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+class ObserverList {
+    private boolean mLocked = false;
+    private static final Object TO_BE_ADDED = new Object();
+    private static final Object TO_BE_REMOVED = new Object();
+    private List<Pair<LifecycleObserver, Object>> mPendingModifications = new ArrayList<>(0);
+    private List<Pair<LifecycleObserver, GenericLifecycleObserver>> mData = new ArrayList<>(5);
+
+    private static final String ERR_RE_ADD = "Trying to re-add an already existing observer. "
+            + "Ignoring the call for ";
+    private static final String WARN_RE_REMOVE = "Trying to remove a non-existing observer. ";
+
+    private boolean exists(LifecycleObserver observer) {
+        final int size = mData.size();
+        for (int i = 0; i < size; i++) {
+            Pair<LifecycleObserver, GenericLifecycleObserver> pair = mData.get(i);
+            if (pair.first == observer) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean hasPendingRemoval(LifecycleObserver observer) {
+        final int size = mPendingModifications.size();
+        for (int i = 0; i < size; i++) {
+            Pair<LifecycleObserver, Object> pair = mPendingModifications.get(i);
+            if (pair.first == observer && pair.second == TO_BE_REMOVED) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    void add(LifecycleObserver observer) {
+        if (!mLocked) {
+            addInternal(observer);
+            return;
+        }
+        mPendingModifications.add(new Pair<>(observer, TO_BE_ADDED));
+    }
+
+    private void addInternal(LifecycleObserver observer) {
+        if (exists(observer)) {
+            Log.e(LifecycleRegistry.TAG, ERR_RE_ADD + observer);
+            return;
+        }
+        GenericLifecycleObserver genericObserver = Lifecycling.getCallback(observer);
+        mData.add(new Pair<>(observer, genericObserver));
+    }
+
+    void remove(LifecycleObserver observer) {
+        if (!mLocked) {
+            removeInternal(observer);
+            return;
+        }
+        mPendingModifications.add(new Pair<>(observer, TO_BE_REMOVED));
+    }
+
+    private void removeInternal(LifecycleObserver observer) {
+        Iterator<Pair<LifecycleObserver, GenericLifecycleObserver>> iterator = mData.iterator();
+        while (iterator.hasNext()) {
+            if (iterator.next().first == observer) {
+                iterator.remove();
+                return;
+            }
+        }
+        Log.w(LifecycleRegistry.TAG, WARN_RE_REMOVE + observer);
+    }
+
+    void forEach(Callback func) {
+        mLocked = true;
+        try {
+            final int size = mData.size();
+            for (int i = 0; i < size; i++) {
+                Pair<LifecycleObserver, GenericLifecycleObserver> pair = mData.get(i);
+                if (hasPendingRemoval(pair.first)) {
+                    continue;
+                }
+                func.run(pair.second);
+            }
+        } finally {
+            mLocked = false;
+            syncPending();
+        }
+    }
+
+    private void syncPending() {
+        // apply everything instead of trying to get clever.
+        // it is unlikely for someone to add/rm same item and when that happens, it is important
+        // to be consistent in the behavior
+        final int size = mPendingModifications.size();
+        //noinspection StatementWithEmptyBody
+        for (int i = 0; i < size; i++) {
+            Pair<LifecycleObserver, Object> pair = mPendingModifications.get(i);
+            if (pair.second == TO_BE_REMOVED) {
+                removeInternal(pair.first);
+            } else { // TO_BE_ADDED
+                addInternal(pair.first);
+            }
+        }
+        mPendingModifications.clear();
+    }
+
+    int size() {
+        return mData.size();
+    }
+
+    interface Callback {
+        void run(GenericLifecycleObserver observer);
+    }
+
+}
diff --git a/lifecycle/runtime/src/test/java/com/android/support/lifecycle/LifecycleRegistryTest.java b/lifecycle/runtime/src/test/java/com/android/support/lifecycle/LifecycleRegistryTest.java
new file mode 100644
index 0000000..dd52771
--- /dev/null
+++ b/lifecycle/runtime/src/test/java/com/android/support/lifecycle/LifecycleRegistryTest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.support.lifecycle;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class LifecycleRegistryTest {
+    private LifecycleProvider mLifecycleProvider;
+    private Lifecycle mLifecycle;
+    private LifecycleRegistry mRegistry;
+    @Before
+    public void init() {
+        mLifecycleProvider = mock(LifecycleProvider.class);
+        mLifecycle = mock(Lifecycle.class);
+        when(mLifecycleProvider.getLifecycle()).thenReturn(mLifecycle);
+        mRegistry = new LifecycleRegistry(mLifecycleProvider, Lifecycle.INITIALIZED);
+    }
+    @Test
+    public void addRemove() {
+        LifecycleObserver observer = mock(LifecycleObserver.class);
+        mRegistry.addObserver(observer);
+        assertThat(mRegistry.size(), is(1));
+        mRegistry.removeObserver(observer);
+        assertThat(mRegistry.size(), is(0));
+    }
+
+    @Test
+    public void addGenericAndObserve() {
+        GenericLifecycleObserver generic = mock(GenericLifecycleObserver.class);
+        mRegistry.addObserver(generic);
+        setState(Lifecycle.CREATED);
+        verify(generic).onStateChanged(mLifecycleProvider, Lifecycle.INITIALIZED);
+        reset(generic);
+        setState(Lifecycle.CREATED);
+        verify(generic, never()).onStateChanged(mLifecycleProvider, Lifecycle.INITIALIZED);
+    }
+
+    @Test
+    public void addRegularClass() {
+        TestObserver testObserver = mock(TestObserver.class);
+        mRegistry.addObserver(testObserver);
+        setState(Lifecycle.STARTED);
+        verify(testObserver, never()).onStopped();
+        setState(Lifecycle.STOPPED);
+        verify(testObserver).onStopped();
+    }
+
+    @Test
+    public void add2RemoveOne() {
+        TestObserver observer1 = mock(TestObserver.class);
+        TestObserver observer2 = mock(TestObserver.class);
+        TestObserver observer3 = mock(TestObserver.class);
+        mRegistry.addObserver(observer1);
+        mRegistry.addObserver(observer2);
+        mRegistry.addObserver(observer3);
+
+        setState(Lifecycle.STOPPED);
+
+        verify(observer1).onStopped();
+        verify(observer2).onStopped();
+        verify(observer3).onStopped();
+        reset(observer1, observer2, observer3);
+
+        mRegistry.removeObserver(observer2);
+        setState(Lifecycle.PAUSED);
+
+        setState(Lifecycle.STOPPED);
+        verify(observer1).onStopped();
+        verify(observer2, never()).onStopped();
+        verify(observer3).onStopped();
+    }
+
+    @Test
+    public void removeWhileTraversing() {
+        final TestObserver observer2 = mock(TestObserver.class);
+        TestObserver observer1 = spy(new TestObserver() {
+            @Override
+            public void onStopped() {
+                mRegistry.removeObserver(observer2);
+            }
+        });
+        mRegistry.addObserver(observer1);
+        mRegistry.addObserver(observer2);
+        setState(Lifecycle.STOPPED);
+        verify(observer2, never()).onStopped();
+        verify(observer1).onStopped();
+    }
+
+    private void setState(@Lifecycle.State int state) {
+        when(mLifecycle.getCurrentState()).thenReturn(state);
+        mRegistry.setCurrentState(state);
+    }
+
+    private interface TestObserver extends LifecycleObserver {
+        @OnState(Lifecycle.STOPPED)
+        void onStopped();
+    }
+}
diff --git a/lifecycle/runtime/src/test/java/com/android/support/lifecycle/ObserverListTest.java b/lifecycle/runtime/src/test/java/com/android/support/lifecycle/ObserverListTest.java
new file mode 100644
index 0000000..1ba4902
--- /dev/null
+++ b/lifecycle/runtime/src/test/java/com/android/support/lifecycle/ObserverListTest.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.support.lifecycle;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+@RunWith(JUnit4.class)
+public class ObserverListTest {
+    private ObserverList mObserverList;
+
+    @Before
+    public void init() {
+        mObserverList = new ObserverList();
+    }
+
+    @Test
+    public void add() {
+        GenericLifecycleObserver observer = mock(GenericLifecycleObserver.class);
+        mObserverList.add(observer);
+        assertThat(mObserverList.size(), is(1));
+        assertThat(collect().get(0), is(observer));
+        mObserverList.remove(observer);
+        assertThat(mObserverList.size(), is(0));
+    }
+
+    @Test
+    public void addTwice() {
+        GenericLifecycleObserver observer = mock(GenericLifecycleObserver.class);
+        mObserverList.add(observer);
+        assertThat(mObserverList.size(), is(1));
+        mObserverList.add(observer);
+        assertThat(mObserverList.size(), is(1));
+    }
+
+    @Test
+    public void addTwiceWhileIterating() {
+        final ObserveAll observer = spy(new ObserveAll() {
+            @Override
+            public void onAny() {
+                mObserverList.add(this);
+                mObserverList.add(this);
+                mObserverList.add(this);
+            }
+        });
+        mObserverList.add(observer);
+        mObserverList.forEach(new ObserverList.Callback() {
+            @Override
+            public void run(GenericLifecycleObserver ignored) {
+                observer.onAny();
+            }
+        });
+        verify(observer, times(1)).onAny();
+        Assert.assertThat(mObserverList.size(), is(1));
+    }
+
+    @Test
+    public void addRemoveAdd() {
+        final ObserveAll observer2 = mock(ObserveAll.class);
+        final ObserveAll observer1 = spy(new ObserveAll() {
+            @Override
+            public void onAny() {
+                mObserverList.remove(this);
+                mObserverList.add(this);
+            }
+        });
+        mObserverList.add(observer1);
+        mObserverList.add(observer2);
+        assertThat(collectObservers(),
+                equalTo(Arrays.asList((LifecycleObserver) observer1, observer2)));
+        mObserverList.forEach(new ObserverList.Callback() {
+            @Override
+            public void run(GenericLifecycleObserver glo) {
+                ((ObserveAll) glo.getReceiver()).onAny();
+            }
+        });
+        verify(observer1, times(1)).onAny();
+        verify(observer2, times(1)).onAny();
+        // because 1 has been removed and re-added, it should get the next event after o1
+        assertThat(collectObservers(),
+                equalTo(Arrays.asList((LifecycleObserver) observer2, observer1)));
+    }
+
+    @Test
+    public void remove() {
+        GenericLifecycleObserver observer = mock(GenericLifecycleObserver.class);
+        LifecycleObserver obj = mock(LifecycleObserver.class);
+        when(observer.getReceiver()).thenReturn(obj);
+        mObserverList.add(observer);
+        assertThat(mObserverList.size(), is(1));
+        assertThat(collect().get(0), is(observer));
+        mObserverList.remove(observer);
+        assertThat(mObserverList.size(), is(0));
+    }
+
+    @Test
+    public void removeWhileTraversing() {
+        GenericLifecycleObserver observer1 = mock(GenericLifecycleObserver.class);
+        final GenericLifecycleObserver observer2 = mock(GenericLifecycleObserver.class);
+        mObserverList.add(observer1);
+        mObserverList.add(observer2);
+        final AtomicBoolean first = new AtomicBoolean(true);
+        mObserverList.forEach(new ObserverList.Callback() {
+            @Override
+            public void run(GenericLifecycleObserver observer) {
+                if (first.getAndSet(false)) {
+                    mObserverList.remove(observer2);
+                } else {
+                    fail("should never receive this call");
+                }
+            }
+        });
+    }
+
+    @Test
+    public void removeObjectWithMultipleCallbacksWhenTraversing() {
+        // if the removed object has multiple callbacks and already received one, should receive
+        // all.
+        final AtomicBoolean first = new AtomicBoolean(true);
+        final StartedObserverWith2Methods observer = spy(new StartedObserverWith2Methods() {
+            @Override
+            public void onStarted1() {
+                handle();
+            }
+
+            @Override
+            public void onStarted2() {
+                handle();
+            }
+
+            private void handle() {
+                if (first.getAndSet(false)) {
+                    mObserverList.remove(this);
+                }
+            }
+        });
+        mObserverList.add(observer);
+
+        final LifecycleProvider lifecycleProvider = mock(LifecycleProvider.class);
+        Lifecycle lifecycle = mock(Lifecycle.class);
+        when(lifecycleProvider.getLifecycle()).thenReturn(lifecycle);
+        when(lifecycle.getCurrentState()).thenReturn(Lifecycle.STARTED);
+
+        mObserverList.forEach(new ObserverList.Callback() {
+            @Override
+            public void run(GenericLifecycleObserver observer) {
+                observer.onStateChanged(lifecycleProvider, Lifecycle.CREATED);
+            }
+        });
+
+        verify(observer).onStarted1();
+        verify(observer).onStarted2();
+    }
+
+    private List<GenericLifecycleObserver> collect() {
+        final ArrayList<GenericLifecycleObserver> items = new ArrayList<>();
+        mObserverList.forEach(new ObserverList.Callback() {
+            @Override
+            public void run(GenericLifecycleObserver observer) {
+                items.add(observer);
+            }
+        });
+        return items;
+    }
+
+    private List<LifecycleObserver> collectObservers() {
+        final ArrayList<LifecycleObserver> items = new ArrayList<>();
+        mObserverList.forEach(new ObserverList.Callback() {
+            @Override
+            public void run(GenericLifecycleObserver observer) {
+                items.add((LifecycleObserver) observer.getReceiver());
+            }
+        });
+        return items;
+    }
+
+    @SuppressWarnings("unused")
+    private interface StartedObserverWith2Methods extends LifecycleObserver {
+        @OnState(Lifecycle.STARTED)
+        void onStarted1();
+
+        @OnState(Lifecycle.STARTED)
+        void onStarted2();
+    }
+
+    private interface ObserveAll extends LifecycleObserver {
+        @OnState(Lifecycle.ANY)
+        void onAny();
+    }
+}
diff --git a/lifecycle/settings.gradle b/lifecycle/settings.gradle
index 498bd72..2643340 100644
--- a/lifecycle/settings.gradle
+++ b/lifecycle/settings.gradle
@@ -1,2 +1,4 @@
+include ':extensions'
+include ':runtime'
 include 'common'
 include 'compiler'
\ No newline at end of file