Import Android SDK Platform P [4386628]

/google/data/ro/projects/android/fetch_artifact \
    --bid 4386628 \
    --target sdk_phone_armv7-win_sdk \
    sdk-repo-linux-sources-4386628.zip

AndroidVersion.ApiLevel has been modified to appear as 28

Change-Id: I9b8400ac92116cae4f033d173f7a5682b26ccba9
diff --git a/android/arch/core/executor/AppToolkitTaskExecutor.java b/android/arch/core/executor/ArchTaskExecutor.java
similarity index 88%
rename from android/arch/core/executor/AppToolkitTaskExecutor.java
rename to android/arch/core/executor/ArchTaskExecutor.java
index 7337f74..2401a73 100644
--- a/android/arch/core/executor/AppToolkitTaskExecutor.java
+++ b/android/arch/core/executor/ArchTaskExecutor.java
@@ -29,8 +29,8 @@
  * @hide This API is not final.
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class AppToolkitTaskExecutor extends TaskExecutor {
-    private static volatile AppToolkitTaskExecutor sInstance;
+public class ArchTaskExecutor extends TaskExecutor {
+    private static volatile ArchTaskExecutor sInstance;
 
     @NonNull
     private TaskExecutor mDelegate;
@@ -54,7 +54,7 @@
         }
     };
 
-    private AppToolkitTaskExecutor() {
+    private ArchTaskExecutor() {
         mDefaultTaskExecutor = new DefaultTaskExecutor();
         mDelegate = mDefaultTaskExecutor;
     }
@@ -62,15 +62,15 @@
     /**
      * Returns an instance of the task executor.
      *
-     * @return The singleton AppToolkitTaskExecutor.
+     * @return The singleton ArchTaskExecutor.
      */
-    public static AppToolkitTaskExecutor getInstance() {
+    public static ArchTaskExecutor getInstance() {
         if (sInstance != null) {
             return sInstance;
         }
-        synchronized (AppToolkitTaskExecutor.class) {
+        synchronized (ArchTaskExecutor.class) {
             if (sInstance == null) {
-                sInstance = new AppToolkitTaskExecutor();
+                sInstance = new ArchTaskExecutor();
             }
         }
         return sInstance;
diff --git a/android/arch/core/executor/JunitTaskExecutorRule.java b/android/arch/core/executor/JunitTaskExecutorRule.java
index cd4f8f5..c3366f3 100644
--- a/android/arch/core/executor/JunitTaskExecutorRule.java
+++ b/android/arch/core/executor/JunitTaskExecutorRule.java
@@ -46,11 +46,11 @@
     }
 
     private void beforeStart() {
-        AppToolkitTaskExecutor.getInstance().setDelegate(mTaskExecutor);
+        ArchTaskExecutor.getInstance().setDelegate(mTaskExecutor);
     }
 
     private void afterFinished() {
-        AppToolkitTaskExecutor.getInstance().setDelegate(null);
+        ArchTaskExecutor.getInstance().setDelegate(null);
     }
 
     public TaskExecutor getTaskExecutor() {
diff --git a/android/arch/core/executor/testing/CountingTaskExecutorRule.java b/android/arch/core/executor/testing/CountingTaskExecutorRule.java
index ad930aa..77133d5 100644
--- a/android/arch/core/executor/testing/CountingTaskExecutorRule.java
+++ b/android/arch/core/executor/testing/CountingTaskExecutorRule.java
@@ -16,7 +16,7 @@
 
 package android.arch.core.executor.testing;
 
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
 import android.arch.core.executor.DefaultTaskExecutor;
 import android.os.SystemClock;
 
@@ -39,7 +39,7 @@
     @Override
     protected void starting(Description description) {
         super.starting(description);
-        AppToolkitTaskExecutor.getInstance().setDelegate(new DefaultTaskExecutor() {
+        ArchTaskExecutor.getInstance().setDelegate(new DefaultTaskExecutor() {
             @Override
             public void executeOnDiskIO(Runnable runnable) {
                 super.executeOnDiskIO(new CountingRunnable(runnable));
@@ -55,7 +55,7 @@
     @Override
     protected void finished(Description description) {
         super.finished(description);
-        AppToolkitTaskExecutor.getInstance().setDelegate(null);
+        ArchTaskExecutor.getInstance().setDelegate(null);
     }
 
     private void increment() {
diff --git a/android/arch/core/executor/testing/CountingTaskExecutorRuleTest.java b/android/arch/core/executor/testing/CountingTaskExecutorRuleTest.java
index ad36b9b..a6a5b2e 100644
--- a/android/arch/core/executor/testing/CountingTaskExecutorRuleTest.java
+++ b/android/arch/core/executor/testing/CountingTaskExecutorRuleTest.java
@@ -19,7 +19,7 @@
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
 
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
 import android.support.test.filters.MediumTest;
 import android.support.test.runner.AndroidJUnit4;
 
@@ -115,13 +115,13 @@
 
     private LatchRunnable runOnIO() {
         LatchRunnable latchRunnable = new LatchRunnable();
-        AppToolkitTaskExecutor.getInstance().executeOnDiskIO(latchRunnable);
+        ArchTaskExecutor.getInstance().executeOnDiskIO(latchRunnable);
         return latchRunnable;
     }
 
     private LatchRunnable runOnMain() {
         LatchRunnable latchRunnable = new LatchRunnable();
-        AppToolkitTaskExecutor.getInstance().executeOnMainThread(latchRunnable);
+        ArchTaskExecutor.getInstance().executeOnMainThread(latchRunnable);
         return latchRunnable;
     }
 
diff --git a/android/arch/core/executor/testing/InstantTaskExecutorRule.java b/android/arch/core/executor/testing/InstantTaskExecutorRule.java
index 07dcf1f..f88a3e3 100644
--- a/android/arch/core/executor/testing/InstantTaskExecutorRule.java
+++ b/android/arch/core/executor/testing/InstantTaskExecutorRule.java
@@ -16,7 +16,7 @@
 
 package android.arch.core.executor.testing;
 
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
 import android.arch.core.executor.TaskExecutor;
 
 import org.junit.rules.TestWatcher;
@@ -32,7 +32,7 @@
     @Override
     protected void starting(Description description) {
         super.starting(description);
-        AppToolkitTaskExecutor.getInstance().setDelegate(new TaskExecutor() {
+        ArchTaskExecutor.getInstance().setDelegate(new TaskExecutor() {
             @Override
             public void executeOnDiskIO(Runnable runnable) {
                 runnable.run();
@@ -53,6 +53,6 @@
     @Override
     protected void finished(Description description) {
         super.finished(description);
-        AppToolkitTaskExecutor.getInstance().setDelegate(null);
+        ArchTaskExecutor.getInstance().setDelegate(null);
     }
 }
diff --git a/android/arch/core/executor/testing/InstantTaskExecutorRuleTest.java b/android/arch/core/executor/testing/InstantTaskExecutorRuleTest.java
index 4345fd1..0fdcbfb 100644
--- a/android/arch/core/executor/testing/InstantTaskExecutorRuleTest.java
+++ b/android/arch/core/executor/testing/InstantTaskExecutorRuleTest.java
@@ -18,7 +18,7 @@
 
 import static org.junit.Assert.assertTrue;
 
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
 
 import org.junit.Rule;
 import org.junit.Test;
@@ -46,7 +46,7 @@
                 return null;
             }
         });
-        AppToolkitTaskExecutor.getInstance().executeOnMainThread(check);
+        ArchTaskExecutor.getInstance().executeOnMainThread(check);
         check.get(1, TimeUnit.SECONDS);
     }
 
@@ -60,7 +60,7 @@
                 return null;
             }
         });
-        AppToolkitTaskExecutor.getInstance().executeOnDiskIO(check);
+        ArchTaskExecutor.getInstance().executeOnDiskIO(check);
         check.get(1, TimeUnit.SECONDS);
     }
 }
diff --git a/android/arch/lifecycle/ClassesInfoCache.java b/android/arch/lifecycle/ClassesInfoCache.java
new file mode 100644
index 0000000..f077dae
--- /dev/null
+++ b/android/arch/lifecycle/ClassesInfoCache.java
@@ -0,0 +1,237 @@
+/*
+ * 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.support.annotation.Nullable;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Reflection is expensive, so we cache information about methods
+ * for {@link ReflectiveGenericLifecycleObserver}, so it can call them,
+ * and for {@link Lifecycling} to determine which observer adapter to use.
+ */
+class ClassesInfoCache {
+
+    static ClassesInfoCache sInstance = new ClassesInfoCache();
+
+    private static final int CALL_TYPE_NO_ARG = 0;
+    private static final int CALL_TYPE_PROVIDER = 1;
+    private static final int CALL_TYPE_PROVIDER_WITH_EVENT = 2;
+
+    private final Map<Class, CallbackInfo> mCallbackMap = new HashMap<>();
+    private final Map<Class, Boolean> mHasLifecycleMethods = new HashMap<>();
+
+    boolean hasLifecycleMethods(Class klass) {
+        if (mHasLifecycleMethods.containsKey(klass)) {
+            return mHasLifecycleMethods.get(klass);
+        }
+
+        Method[] methods = klass.getDeclaredMethods();
+        for (Method method : methods) {
+            OnLifecycleEvent annotation = method.getAnnotation(OnLifecycleEvent.class);
+            if (annotation != null) {
+                // Optimization for reflection, we know that this method is called
+                // when there is no generated adapter. But there are methods with @OnLifecycleEvent
+                // so we know that will use ReflectiveGenericLifecycleObserver,
+                // so we createInfo in advance.
+                // CreateInfo always initialize mHasLifecycleMethods for a class, so we don't do it
+                // here.
+                createInfo(klass, methods);
+                return true;
+            }
+        }
+        mHasLifecycleMethods.put(klass, false);
+        return false;
+    }
+
+    CallbackInfo getInfo(Class klass) {
+        CallbackInfo existing = mCallbackMap.get(klass);
+        if (existing != null) {
+            return existing;
+        }
+        existing = createInfo(klass, null);
+        return existing;
+    }
+
+    private void verifyAndPutHandler(Map<MethodReference, Lifecycle.Event> handlers,
+            MethodReference newHandler, Lifecycle.Event newEvent, Class klass) {
+        Lifecycle.Event event = handlers.get(newHandler);
+        if (event != null && newEvent != event) {
+            Method method = newHandler.mMethod;
+            throw new IllegalArgumentException(
+                    "Method " + method.getName() + " in " + klass.getName()
+                            + " already declared with different @OnLifecycleEvent value: previous"
+                            + " value " + event + ", new value " + newEvent);
+        }
+        if (event == null) {
+            handlers.put(newHandler, newEvent);
+        }
+    }
+
+    private CallbackInfo createInfo(Class klass, @Nullable Method[] declaredMethods) {
+        Class superclass = klass.getSuperclass();
+        Map<MethodReference, Lifecycle.Event> handlerToEvent = new HashMap<>();
+        if (superclass != null) {
+            CallbackInfo superInfo = getInfo(superclass);
+            if (superInfo != null) {
+                handlerToEvent.putAll(superInfo.mHandlerToEvent);
+            }
+        }
+
+        Class[] interfaces = klass.getInterfaces();
+        for (Class intrfc : interfaces) {
+            for (Map.Entry<MethodReference, Lifecycle.Event> entry : getInfo(
+                    intrfc).mHandlerToEvent.entrySet()) {
+                verifyAndPutHandler(handlerToEvent, entry.getKey(), entry.getValue(), klass);
+            }
+        }
+
+        Method[] methods = declaredMethods != null ? declaredMethods : klass.getDeclaredMethods();
+        boolean hasLifecycleMethods = false;
+        for (Method method : methods) {
+            OnLifecycleEvent annotation = method.getAnnotation(OnLifecycleEvent.class);
+            if (annotation == null) {
+                continue;
+            }
+            hasLifecycleMethods = true;
+            Class<?>[] params = method.getParameterTypes();
+            int callType = CALL_TYPE_NO_ARG;
+            if (params.length > 0) {
+                callType = CALL_TYPE_PROVIDER;
+                if (!params[0].isAssignableFrom(LifecycleOwner.class)) {
+                    throw new IllegalArgumentException(
+                            "invalid parameter type. Must be one and instanceof LifecycleOwner");
+                }
+            }
+            Lifecycle.Event event = annotation.value();
+
+            if (params.length > 1) {
+                callType = CALL_TYPE_PROVIDER_WITH_EVENT;
+                if (!params[1].isAssignableFrom(Lifecycle.Event.class)) {
+                    throw new IllegalArgumentException(
+                            "invalid parameter type. second arg must be an event");
+                }
+                if (event != Lifecycle.Event.ON_ANY) {
+                    throw new IllegalArgumentException(
+                            "Second arg is supported only for ON_ANY value");
+                }
+            }
+            if (params.length > 2) {
+                throw new IllegalArgumentException("cannot have more than 2 params");
+            }
+            MethodReference methodReference = new MethodReference(callType, method);
+            verifyAndPutHandler(handlerToEvent, methodReference, event, klass);
+        }
+        CallbackInfo info = new CallbackInfo(handlerToEvent);
+        mCallbackMap.put(klass, info);
+        mHasLifecycleMethods.put(klass, hasLifecycleMethods);
+        return info;
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    static class CallbackInfo {
+        final Map<Lifecycle.Event, List<MethodReference>> mEventToHandlers;
+        final Map<MethodReference, Lifecycle.Event> mHandlerToEvent;
+
+        CallbackInfo(Map<MethodReference, Lifecycle.Event> handlerToEvent) {
+            mHandlerToEvent = handlerToEvent;
+            mEventToHandlers = new HashMap<>();
+            for (Map.Entry<MethodReference, Lifecycle.Event> entry : handlerToEvent.entrySet()) {
+                Lifecycle.Event event = entry.getValue();
+                List<MethodReference> methodReferences = mEventToHandlers.get(event);
+                if (methodReferences == null) {
+                    methodReferences = new ArrayList<>();
+                    mEventToHandlers.put(event, methodReferences);
+                }
+                methodReferences.add(entry.getKey());
+            }
+        }
+
+        @SuppressWarnings("ConstantConditions")
+        void invokeCallbacks(LifecycleOwner source, Lifecycle.Event event, Object target) {
+            invokeMethodsForEvent(mEventToHandlers.get(event), source, event, target);
+            invokeMethodsForEvent(mEventToHandlers.get(Lifecycle.Event.ON_ANY), source, event,
+                    target);
+        }
+
+        private static void invokeMethodsForEvent(List<MethodReference> handlers,
+                LifecycleOwner source, Lifecycle.Event event, Object mWrapped) {
+            if (handlers != null) {
+                for (int i = handlers.size() - 1; i >= 0; i--) {
+                    handlers.get(i).invokeCallback(source, event, mWrapped);
+                }
+            }
+        }
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    static class MethodReference {
+        final int mCallType;
+        final Method mMethod;
+
+        MethodReference(int callType, Method method) {
+            mCallType = callType;
+            mMethod = method;
+            mMethod.setAccessible(true);
+        }
+
+        void invokeCallback(LifecycleOwner source, Lifecycle.Event event, Object target) {
+            //noinspection TryWithIdenticalCatches
+            try {
+                switch (mCallType) {
+                    case CALL_TYPE_NO_ARG:
+                        mMethod.invoke(target);
+                        break;
+                    case CALL_TYPE_PROVIDER:
+                        mMethod.invoke(target, source);
+                        break;
+                    case CALL_TYPE_PROVIDER_WITH_EVENT:
+                        mMethod.invoke(target, source, event);
+                        break;
+                }
+            } catch (InvocationTargetException e) {
+                throw new RuntimeException("Failed to call observer method", e.getCause());
+            } catch (IllegalAccessException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+
+            MethodReference that = (MethodReference) o;
+            return mCallType == that.mCallType && mMethod.getName().equals(that.mMethod.getName());
+        }
+
+        @Override
+        public int hashCode() {
+            return 31 * mCallType + mMethod.getName().hashCode();
+        }
+    }
+}
diff --git a/android/arch/lifecycle/CompositeGeneratedAdaptersObserver.java b/android/arch/lifecycle/CompositeGeneratedAdaptersObserver.java
new file mode 100644
index 0000000..e8cbe7c
--- /dev/null
+++ b/android/arch/lifecycle/CompositeGeneratedAdaptersObserver.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.support.annotation.RestrictTo;
+
+/**
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class CompositeGeneratedAdaptersObserver implements GenericLifecycleObserver {
+
+    private final GeneratedAdapter[] mGeneratedAdapters;
+
+    CompositeGeneratedAdaptersObserver(GeneratedAdapter[] generatedAdapters) {
+        mGeneratedAdapters = generatedAdapters;
+    }
+
+    @Override
+    public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
+        MethodCallsLogger logger = new MethodCallsLogger();
+        for (GeneratedAdapter mGenerated: mGeneratedAdapters) {
+            mGenerated.callMethods(source, event, false, logger);
+        }
+        for (GeneratedAdapter mGenerated: mGeneratedAdapters) {
+            mGenerated.callMethods(source, event, true, logger);
+        }
+    }
+}
diff --git a/android/arch/lifecycle/ComputableLiveData.java b/android/arch/lifecycle/ComputableLiveData.java
index fe18243..f135244 100644
--- a/android/arch/lifecycle/ComputableLiveData.java
+++ b/android/arch/lifecycle/ComputableLiveData.java
@@ -1,136 +1,9 @@
-/*
- * 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.
- */
-
+//ComputableLiveData interface for tests
 package android.arch.lifecycle;
-
-import android.arch.core.executor.AppToolkitTaskExecutor;
-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)
+import android.arch.lifecycle.LiveData;
 public abstract class ComputableLiveData<T> {
-
-    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
-                AppToolkitTaskExecutor.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.
-                    AppToolkitTaskExecutor.getInstance().executeOnDiskIO(mRefreshRunnable);
-                }
-            }
-        }
-    };
-
-    /**
-     * Invalidates the LiveData.
-     * <p>
-     * When there are active observers, this will trigger a call to {@link #compute()}.
-     */
-    public void invalidate() {
-        AppToolkitTaskExecutor.getInstance().executeOnMainThread(mInvalidationRunnable);
-    }
-
-    @SuppressWarnings("WeakerAccess")
-    @WorkerThread
-    protected abstract T compute();
+    public ComputableLiveData(){}
+    abstract protected T compute();
+    public LiveData<T> getLiveData() {return null;}
+    public void invalidate() {}
 }
diff --git a/android/arch/lifecycle/ComputableLiveDataTest.java b/android/arch/lifecycle/ComputableLiveDataTest.java
index 0a3fbed..eb89d8d 100644
--- a/android/arch/lifecycle/ComputableLiveDataTest.java
+++ b/android/arch/lifecycle/ComputableLiveDataTest.java
@@ -27,7 +27,7 @@
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
 
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
 import android.arch.core.executor.TaskExecutor;
 import android.arch.core.executor.TaskExecutorWithFakeMainThread;
 import android.arch.lifecycle.util.InstantTaskExecutor;
@@ -58,12 +58,12 @@
     @Before
     public void swapExecutorDelegate() {
         mTaskExecutor = spy(new InstantTaskExecutor());
-        AppToolkitTaskExecutor.getInstance().setDelegate(mTaskExecutor);
+        ArchTaskExecutor.getInstance().setDelegate(mTaskExecutor);
     }
 
     @After
     public void removeExecutorDelegate() {
-        AppToolkitTaskExecutor.getInstance().setDelegate(null);
+        ArchTaskExecutor.getInstance().setDelegate(null);
     }
 
     @Test
@@ -76,7 +76,7 @@
     @Test
     public void noConcurrentCompute() throws InterruptedException {
         TaskExecutorWithFakeMainThread executor = new TaskExecutorWithFakeMainThread(2);
-        AppToolkitTaskExecutor.getInstance().setDelegate(executor);
+        ArchTaskExecutor.getInstance().setDelegate(executor);
         try {
             // # of compute calls
             final Semaphore computeCounter = new Semaphore(0);
@@ -121,7 +121,7 @@
             // assert no other results arrive
             verify(observer, never()).onChanged(anyInt());
         } finally {
-            AppToolkitTaskExecutor.getInstance().setDelegate(null);
+            ArchTaskExecutor.getInstance().setDelegate(null);
         }
     }
 
diff --git a/android/arch/lifecycle/DefaultLifecycleObserver.java b/android/arch/lifecycle/DefaultLifecycleObserver.java
new file mode 100644
index 0000000..b6f468c
--- /dev/null
+++ b/android/arch/lifecycle/DefaultLifecycleObserver.java
@@ -0,0 +1,100 @@
+/*
+ * 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.support.annotation.NonNull;
+
+/**
+ * Callback interface for listening to {@link LifecycleOwner} state changes.
+ * <p>
+ * If you use Java 8 language, <b>always</b> prefer it over annotations.
+ */
+@SuppressWarnings("unused")
+public interface DefaultLifecycleObserver extends FullLifecycleObserver {
+
+    /**
+     * Notifies that {@code ON_CREATE} event occurred.
+     * <p>
+     * This method will be called after the {@link LifecycleOwner}'s {@code onCreate}
+     * method returns.
+     *
+     * @param owner the component, whose state was changed
+     */
+    @Override
+    default void onCreate(@NonNull LifecycleOwner owner) {
+    }
+
+    /**
+     * Notifies that {@code ON_START} event occurred.
+     * <p>
+     * This method will be called after the {@link LifecycleOwner}'s {@code onStart} method returns.
+     *
+     * @param owner the component, whose state was changed
+     */
+    @Override
+    default void onStart(@NonNull LifecycleOwner owner) {
+    }
+
+    /**
+     * Notifies that {@code ON_RESUME} event occurred.
+     * <p>
+     * This method will be called after the {@link LifecycleOwner}'s {@code onResume}
+     * method returns.
+     *
+     * @param owner the component, whose state was changed
+     */
+    @Override
+    default void onResume(@NonNull LifecycleOwner owner) {
+    }
+
+    /**
+     * Notifies that {@code ON_PAUSE} event occurred.
+     * <p>
+     * This method will be called before the {@link LifecycleOwner}'s {@code onPause} method
+     * is called.
+     *
+     * @param owner the component, whose state was changed
+     */
+    @Override
+    default void onPause(@NonNull LifecycleOwner owner) {
+    }
+
+    /**
+     * Notifies that {@code ON_STOP} event occurred.
+     * <p>
+     * This method will be called before the {@link LifecycleOwner}'s {@code onStop} method
+     * is called.
+     *
+     * @param owner the component, whose state was changed
+     */
+    @Override
+    default void onStop(@NonNull LifecycleOwner owner) {
+    }
+
+    /**
+     * Notifies that {@code ON_DESTROY} event occurred.
+     * <p>
+     * This method will be called before the {@link LifecycleOwner}'s {@code onStop} method
+     * is called.
+     *
+     * @param owner the component, whose state was changed
+     */
+    @Override
+    default void onDestroy(@NonNull LifecycleOwner owner) {
+    }
+}
+
diff --git a/android/arch/lifecycle/FragmentInBackStackLifecycleTest.java b/android/arch/lifecycle/FragmentInBackStackLifecycleTest.java
index 3397f5f..f48f788 100644
--- a/android/arch/lifecycle/FragmentInBackStackLifecycleTest.java
+++ b/android/arch/lifecycle/FragmentInBackStackLifecycleTest.java
@@ -37,6 +37,7 @@
 import android.support.test.filters.SmallTest;
 import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.app.Fragment;
 import android.support.v4.app.FragmentActivity;
 import android.support.v4.app.FragmentManager;
 
@@ -58,20 +59,20 @@
         final ArrayList<Event> collectedEvents = new ArrayList<>();
         LifecycleObserver collectingObserver = new LifecycleObserver() {
             @OnLifecycleEvent(Event.ON_ANY)
-            void onAny(LifecycleOwner owner, Event event) {
+            void onAny(@SuppressWarnings("unused") LifecycleOwner owner, Event event) {
                 collectedEvents.add(event);
             }
         };
         final FragmentActivity activity = activityTestRule.getActivity();
         activityTestRule.runOnUiThread(() -> {
             FragmentManager fm = activity.getSupportFragmentManager();
-            LifecycleFragment fragment = new LifecycleFragment();
+            Fragment fragment = new Fragment();
             fm.beginTransaction().add(R.id.fragment_container, fragment, "tag").addToBackStack(null)
                     .commit();
             fm.executePendingTransactions();
 
             fragment.getLifecycle().addObserver(collectingObserver);
-            LifecycleFragment fragment2 = new LifecycleFragment();
+            Fragment fragment2 = new Fragment();
             fm.beginTransaction().replace(R.id.fragment_container, fragment2).addToBackStack(null)
                     .commit();
             fm.executePendingTransactions();
@@ -82,12 +83,13 @@
         EmptyActivity newActivity = recreateActivity(activityTestRule.getActivity(),
                 activityTestRule);
 
+        //noinspection ArraysAsListWithZeroOrOneArgument
         assertThat(collectedEvents, is(asList(ON_DESTROY)));
         collectedEvents.clear();
         EmptyActivity lastActivity = recreateActivity(newActivity, activityTestRule);
         activityTestRule.runOnUiThread(() -> {
             FragmentManager fm = lastActivity.getSupportFragmentManager();
-            LifecycleFragment fragment = (LifecycleFragment) fm.findFragmentByTag("tag");
+            Fragment fragment = fm.findFragmentByTag("tag");
             fragment.getLifecycle().addObserver(collectingObserver);
             assertThat(collectedEvents, iterableWithSize(0));
             fm.popBackStackImmediate();
diff --git a/android/arch/lifecycle/FragmentOperationsLifecycleTest.java b/android/arch/lifecycle/FragmentOperationsLifecycleTest.java
index be062cb..3e61277 100644
--- a/android/arch/lifecycle/FragmentOperationsLifecycleTest.java
+++ b/android/arch/lifecycle/FragmentOperationsLifecycleTest.java
@@ -34,6 +34,7 @@
 import android.support.test.filters.MediumTest;
 import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.app.Fragment;
 import android.support.v4.app.FragmentManager;
 
 import org.junit.Rule;
@@ -55,7 +56,7 @@
     @UiThreadTest
     public void addRemoveFragment() {
         EmptyActivity activity = mActivityTestRule.getActivity();
-        LifecycleFragment fragment = new LifecycleFragment();
+        Fragment fragment = new Fragment();
         FragmentManager fm = activity.getSupportFragmentManager();
         fm.beginTransaction().add(fragment, "tag").commitNow();
         CollectingObserver observer = observeAndCollectIn(fragment);
@@ -70,7 +71,7 @@
     @UiThreadTest
     public void fragmentInBackstack() {
         EmptyActivity activity = mActivityTestRule.getActivity();
-        LifecycleFragment fragment1 = new LifecycleFragment();
+        Fragment fragment1 = new Fragment();
         FragmentManager fm = activity.getSupportFragmentManager();
         fm.beginTransaction().add(R.id.fragment_container, fragment1, "tag").addToBackStack(null)
                 .commit();
@@ -78,7 +79,7 @@
         CollectingObserver observer1 = observeAndCollectIn(fragment1);
         assertThat(observer1.getEventsAndReset(), is(asList(ON_CREATE, ON_START, ON_RESUME)));
 
-        LifecycleFragment fragment2 = new LifecycleFragment();
+        Fragment fragment2 = new Fragment();
         fm.beginTransaction().replace(R.id.fragment_container, fragment2).addToBackStack(null)
                 .commit();
         fm.executePendingTransactions();
@@ -95,7 +96,7 @@
         assertThat(observer1.getEventsAndReset(), is(asList(ON_PAUSE, ON_STOP, ON_DESTROY)));
     }
 
-    private static CollectingObserver observeAndCollectIn(LifecycleFragment fragment) {
+    private static CollectingObserver observeAndCollectIn(Fragment fragment) {
         CollectingObserver observer = new CollectingObserver();
         fragment.getLifecycle().addObserver(observer);
         return observer;
diff --git a/android/arch/lifecycle/FullLifecycleObserver.java b/android/arch/lifecycle/FullLifecycleObserver.java
new file mode 100644
index 0000000..f179274
--- /dev/null
+++ b/android/arch/lifecycle/FullLifecycleObserver.java
@@ -0,0 +1,32 @@
+/*
+ * 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;
+
+interface FullLifecycleObserver extends LifecycleObserver {
+
+    void onCreate(LifecycleOwner owner);
+
+    void onStart(LifecycleOwner owner);
+
+    void onResume(LifecycleOwner owner);
+
+    void onPause(LifecycleOwner owner);
+
+    void onStop(LifecycleOwner owner);
+
+    void onDestroy(LifecycleOwner owner);
+}
diff --git a/android/arch/lifecycle/FullLifecycleObserverAdapter.java b/android/arch/lifecycle/FullLifecycleObserverAdapter.java
new file mode 100644
index 0000000..0a91a66
--- /dev/null
+++ b/android/arch/lifecycle/FullLifecycleObserverAdapter.java
@@ -0,0 +1,52 @@
+/*
+ * 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;
+
+class FullLifecycleObserverAdapter implements GenericLifecycleObserver {
+
+    private final FullLifecycleObserver mObserver;
+
+    FullLifecycleObserverAdapter(FullLifecycleObserver observer) {
+        mObserver = observer;
+    }
+
+    @Override
+    public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
+        switch (event) {
+            case ON_CREATE:
+                mObserver.onCreate(source);
+                break;
+            case ON_START:
+                mObserver.onStart(source);
+                break;
+            case ON_RESUME:
+                mObserver.onResume(source);
+                break;
+            case ON_PAUSE:
+                mObserver.onPause(source);
+                break;
+            case ON_STOP:
+                mObserver.onStop(source);
+                break;
+            case ON_DESTROY:
+                mObserver.onDestroy(source);
+                break;
+            case ON_ANY:
+                throw new IllegalArgumentException("ON_ANY must not been send by anybody");
+        }
+    }
+}
diff --git a/android/arch/lifecycle/FullLifecycleObserverTest.java b/android/arch/lifecycle/FullLifecycleObserverTest.java
new file mode 100644
index 0000000..def6755
--- /dev/null
+++ b/android/arch/lifecycle/FullLifecycleObserverTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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 static android.arch.lifecycle.Lifecycle.State.INITIALIZED;
+import static android.arch.lifecycle.Lifecycle.State.RESUMED;
+import static android.arch.lifecycle.Lifecycle.State.STARTED;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.when;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.InOrder;
+import org.mockito.Mockito;
+
+@RunWith(JUnit4.class)
+public class FullLifecycleObserverTest {
+    private LifecycleOwner mOwner;
+    private Lifecycle mLifecycle;
+
+    @Before
+    public void initMocks() {
+        mOwner = mock(LifecycleOwner.class);
+        mLifecycle = mock(Lifecycle.class);
+        when(mOwner.getLifecycle()).thenReturn(mLifecycle);
+    }
+
+    @Test
+    public void eachEvent() {
+        FullLifecycleObserver obj = mock(FullLifecycleObserver.class);
+        FullLifecycleObserverAdapter observer = new FullLifecycleObserverAdapter(obj);
+        when(mLifecycle.getCurrentState()).thenReturn(CREATED);
+
+        observer.onStateChanged(mOwner, ON_CREATE);
+        InOrder inOrder = Mockito.inOrder(obj);
+        inOrder.verify(obj).onCreate(mOwner);
+        reset(obj);
+
+        when(mLifecycle.getCurrentState()).thenReturn(STARTED);
+        observer.onStateChanged(mOwner, ON_START);
+        inOrder.verify(obj).onStart(mOwner);
+        reset(obj);
+
+        when(mLifecycle.getCurrentState()).thenReturn(RESUMED);
+        observer.onStateChanged(mOwner, ON_RESUME);
+        inOrder.verify(obj).onResume(mOwner);
+        reset(obj);
+
+        when(mLifecycle.getCurrentState()).thenReturn(STARTED);
+        observer.onStateChanged(mOwner, ON_PAUSE);
+        inOrder.verify(obj).onPause(mOwner);
+        reset(obj);
+
+        when(mLifecycle.getCurrentState()).thenReturn(CREATED);
+        observer.onStateChanged(mOwner, ON_STOP);
+        inOrder.verify(obj).onStop(mOwner);
+        reset(obj);
+
+        when(mLifecycle.getCurrentState()).thenReturn(INITIALIZED);
+        observer.onStateChanged(mOwner, ON_DESTROY);
+        inOrder.verify(obj).onDestroy(mOwner);
+        reset(obj);
+    }
+}
diff --git a/android/arch/lifecycle/GeneratedAdapter.java b/android/arch/lifecycle/GeneratedAdapter.java
new file mode 100644
index 0000000..a8862da
--- /dev/null
+++ b/android/arch/lifecycle/GeneratedAdapter.java
@@ -0,0 +1,38 @@
+/*
+ * 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.support.annotation.RestrictTo;
+
+/**
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public interface GeneratedAdapter {
+
+    /**
+     * Called when a state transition event happens.
+     *
+     * @param source The source of the event
+     * @param event The event
+     * @param onAny approveCall onAny handlers
+     * @param logger if passed, used to track called methods and prevent calling the same method
+     *              twice
+     */
+    void callMethods(LifecycleOwner source, Lifecycle.Event event, boolean onAny,
+            MethodCallsLogger logger);
+}
diff --git a/android/arch/lifecycle/GeneratedAdaptersTest.java b/android/arch/lifecycle/GeneratedAdaptersTest.java
new file mode 100644
index 0000000..2abb511
--- /dev/null
+++ b/android/arch/lifecycle/GeneratedAdaptersTest.java
@@ -0,0 +1,212 @@
+/*
+ * 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_ANY;
+import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.singletonList;
+
+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.List;
+
+@RunWith(JUnit4.class)
+public class GeneratedAdaptersTest {
+
+    private LifecycleOwner mOwner;
+    @SuppressWarnings("FieldCanBeLocal")
+    private Lifecycle mLifecycle;
+
+    @Before
+    public void initMocks() {
+        mOwner = mock(LifecycleOwner.class);
+        mLifecycle = mock(Lifecycle.class);
+        when(mOwner.getLifecycle()).thenReturn(mLifecycle);
+    }
+
+    static class SimpleObserver implements LifecycleObserver {
+        List<String> mLog;
+
+        SimpleObserver(List<String> log) {
+            mLog = log;
+        }
+
+        @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
+        void onCreate() {
+            mLog.add("onCreate");
+        }
+    }
+
+    @Test
+    public void testSimpleSingleGeneratedAdapter() {
+        List<String>  actual = new ArrayList<>();
+        GenericLifecycleObserver callback = Lifecycling.getCallback(new SimpleObserver(actual));
+        callback.onStateChanged(mOwner, Lifecycle.Event.ON_CREATE);
+        assertThat(callback, instanceOf(SingleGeneratedAdapterObserver.class));
+        assertThat(actual, is(singletonList("onCreate")));
+    }
+
+    static class TestObserver implements LifecycleObserver {
+        List<String> mLog;
+
+        TestObserver(List<String> log) {
+            mLog = log;
+        }
+
+        @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
+        void onCreate() {
+            mLog.add("onCreate");
+        }
+
+        @OnLifecycleEvent(ON_ANY)
+        void onAny() {
+            mLog.add("onAny");
+        }
+    }
+
+    @Test
+    public void testOnAny() {
+        List<String>  actual = new ArrayList<>();
+        GenericLifecycleObserver callback = Lifecycling.getCallback(new TestObserver(actual));
+        callback.onStateChanged(mOwner, Lifecycle.Event.ON_CREATE);
+        assertThat(callback, instanceOf(SingleGeneratedAdapterObserver.class));
+        assertThat(actual, is(asList("onCreate", "onAny")));
+    }
+
+    interface OnPauses extends LifecycleObserver {
+        @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
+        void onPause();
+
+        @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
+        void onPause(LifecycleOwner owner);
+    }
+
+    interface OnPauseResume extends LifecycleObserver {
+        @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
+        void onPause();
+
+        @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
+        void onResume();
+    }
+
+    class Impl1 implements OnPauses, OnPauseResume {
+
+        List<String> mLog;
+
+        Impl1(List<String> log) {
+            mLog = log;
+        }
+
+        @Override
+        public void onPause() {
+            mLog.add("onPause_0");
+        }
+
+        @Override
+        public void onResume() {
+            mLog.add("onResume");
+        }
+
+        @Override
+        public void onPause(LifecycleOwner owner) {
+            mLog.add("onPause_1");
+        }
+    }
+
+    @Test
+    public void testClashingInterfaces() {
+        List<String>  actual = new ArrayList<>();
+        GenericLifecycleObserver callback = Lifecycling.getCallback(new Impl1(actual));
+        callback.onStateChanged(mOwner, Lifecycle.Event.ON_PAUSE);
+        assertThat(callback, instanceOf(CompositeGeneratedAdaptersObserver.class));
+        assertThat(actual, is(asList("onPause_0", "onPause_1")));
+        actual.clear();
+        callback.onStateChanged(mOwner, Lifecycle.Event.ON_RESUME);
+        assertThat(actual, is(singletonList("onResume")));
+    }
+
+    class Base implements LifecycleObserver {
+
+        List<String> mLog;
+
+        Base(List<String> log) {
+            mLog = log;
+        }
+
+        @OnLifecycleEvent(ON_ANY)
+        void onAny() {
+            mLog.add("onAny_0");
+        }
+
+        @OnLifecycleEvent(ON_ANY)
+        void onAny(LifecycleOwner owner) {
+            mLog.add("onAny_1");
+        }
+
+        @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
+        void onResume() {
+            mLog.add("onResume");
+        }
+    }
+
+    interface OnAny extends LifecycleObserver {
+        @OnLifecycleEvent(ON_ANY)
+        void onAny();
+
+        @OnLifecycleEvent(ON_ANY)
+        void onAny(LifecycleOwner owner, Lifecycle.Event event);
+    }
+
+    class Derived extends Base implements OnAny {
+        Derived(List<String> log) {
+            super(log);
+        }
+
+        @Override
+        public void onAny() {
+            super.onAny();
+        }
+
+        @Override
+        public void onAny(LifecycleOwner owner, Lifecycle.Event event) {
+            mLog.add("onAny_2");
+            assertThat(event, is(ON_RESUME));
+        }
+    }
+
+    @Test
+    public void testClashingClassAndInterface() {
+        List<String>  actual = new ArrayList<>();
+        GenericLifecycleObserver callback = Lifecycling.getCallback(new Derived(actual));
+        callback.onStateChanged(mOwner, Lifecycle.Event.ON_RESUME);
+        assertThat(callback, instanceOf(CompositeGeneratedAdaptersObserver.class));
+        assertThat(actual, is(asList("onResume", "onAny_0", "onAny_1", "onAny_2")));
+    }
+
+}
diff --git a/android/arch/lifecycle/Lifecycle.java b/android/arch/lifecycle/Lifecycle.java
index fcbd50a..02db5ff 100644
--- a/android/arch/lifecycle/Lifecycle.java
+++ b/android/arch/lifecycle/Lifecycle.java
@@ -34,7 +34,21 @@
  * before {@link android.app.Activity#onStop onStop} is called.
  * This gives you certain guarantees on which state the owner is in.
  * <p>
- * Lifecycle events are observed using annotations.
+ * If you use <b>Java 8 Language</b>, then observe events with {@link DefaultLifecycleObserver}.
+ * To include it you should add {@code "android.arch.lifecycle:common-java8:<version>"} to your
+ * build.gradle file.
+ * <pre>
+ * class TestObserver implements DefaultLifecycleObserver {
+ *     {@literal @}Override
+ *     public void onCreate(LifecycleOwner owner) {
+ *         // your code
+ *     }
+ * }
+ * </pre>
+ * If you use <b>Java 7 Language</b>, Lifecycle events are observed using annotations.
+ * Once Java 8 Language becomes mainstream on Android, annotations will be deprecated, so between
+ * {@link DefaultLifecycleObserver} and annotations,
+ * you must always prefer {@code DefaultLifecycleObserver}.
  * <pre>
  * class TestObserver implements LifecycleObserver {
  *   {@literal @}OnLifecycleEvent(ON_STOP)
@@ -42,16 +56,6 @@
  * }
  * </pre>
  * <p>
- * Multiple methods can observe the same event.
- * <pre>
- * class TestObserver implements LifecycleObserver {
- *   {@literal @}OnLifecycleEvent(ON_STOP)
- *   void onStoppedFirst() {}
- *   {@literal @}OnLifecycleEvent(ON_STOP)
- *   void onStoppedSecond() {}
- * }
- * </pre>
- * <p>
  * Observer methods can receive zero or one argument.
  * If used, the first argument must be of type {@link LifecycleOwner}.
  * Methods annotated with {@link Event#ON_ANY} can receive the second argument, which must be
diff --git a/android/arch/lifecycle/Lifecycling.java b/android/arch/lifecycle/Lifecycling.java
index 3a5c0b9..7d6b37f 100644
--- a/android/arch/lifecycle/Lifecycling.java
+++ b/android/arch/lifecycle/Lifecycling.java
@@ -22,52 +22,61 @@
 
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 /**
  * Internal class to handle lifecycle conversion etc.
+ *
  * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-class Lifecycling {
-    private static Constructor<? extends GenericLifecycleObserver> sREFLECTIVE;
+public class Lifecycling {
 
-    static {
-        try {
-            sREFLECTIVE = ReflectiveGenericLifecycleObserver.class
-                    .getDeclaredConstructor(Object.class);
-        } catch (NoSuchMethodException ignored) {
+    private static final int REFLECTIVE_CALLBACK = 1;
+    private static final int GENERATED_CALLBACK = 2;
 
-        }
-    }
-
-    private static Map<Class, Constructor<? extends GenericLifecycleObserver>> sCallbackCache =
+    private static Map<Class, Integer> sCallbackCache = new HashMap<>();
+    private static Map<Class, List<Constructor<? extends GeneratedAdapter>>> sClassToAdapters =
             new HashMap<>();
 
     @NonNull
     static GenericLifecycleObserver getCallback(Object object) {
+        if (object instanceof FullLifecycleObserver) {
+            return new FullLifecycleObserverAdapter((FullLifecycleObserver) object);
+        }
+
         if (object instanceof GenericLifecycleObserver) {
             return (GenericLifecycleObserver) object;
         }
+
+        final Class<?> klass = object.getClass();
+        int type = getObserverConstructorType(klass);
+        if (type == GENERATED_CALLBACK) {
+            List<Constructor<? extends GeneratedAdapter>> constructors =
+                    sClassToAdapters.get(klass);
+            if (constructors.size() == 1) {
+                GeneratedAdapter generatedAdapter = createGeneratedAdapter(
+                        constructors.get(0), object);
+                return new SingleGeneratedAdapterObserver(generatedAdapter);
+            }
+            GeneratedAdapter[] adapters = new GeneratedAdapter[constructors.size()];
+            for (int i = 0; i < constructors.size(); i++) {
+                adapters[i] = createGeneratedAdapter(constructors.get(i), object);
+            }
+            return new CompositeGeneratedAdaptersObserver(adapters);
+        }
+        return new ReflectiveGenericLifecycleObserver(object);
+    }
+
+    private static GeneratedAdapter createGeneratedAdapter(
+            Constructor<? extends GeneratedAdapter> constructor, Object object) {
         //noinspection TryWithIdenticalCatches
         try {
-            final Class<?> klass = object.getClass();
-            Constructor<? extends GenericLifecycleObserver> cachedConstructor = sCallbackCache.get(
-                    klass);
-            if (cachedConstructor != null) {
-                return cachedConstructor.newInstance(object);
-            }
-            cachedConstructor = getGeneratedAdapterConstructor(klass);
-            if (cachedConstructor != null) {
-                if (!cachedConstructor.isAccessible()) {
-                    cachedConstructor.setAccessible(true);
-                }
-            } else {
-                cachedConstructor = sREFLECTIVE;
-            }
-            sCallbackCache.put(klass, cachedConstructor);
-            return cachedConstructor.newInstance(object);
+            return constructor.newInstance(object);
         } catch (IllegalAccessException e) {
             throw new RuntimeException(e);
         } catch (InstantiationException e) {
@@ -78,37 +87,95 @@
     }
 
     @Nullable
-    private static Constructor<? extends GenericLifecycleObserver> getGeneratedAdapterConstructor(
-            Class<?> klass) {
-        Package aPackage = klass.getPackage();
-        final String fullPackage = aPackage != null ? aPackage.getName() : "";
-
-        String name = klass.getCanonicalName();
-        // anonymous class bug:35073837
-        if (name == null) {
-            return null;
-        }
-        final String adapterName = getAdapterName(fullPackage.isEmpty() ? name :
-                name.substring(fullPackage.length() + 1));
+    private static Constructor<? extends GeneratedAdapter> generatedConstructor(Class<?> klass) {
         try {
-            @SuppressWarnings("unchecked")
-            final Class<? extends GenericLifecycleObserver> aClass =
-                    (Class<? extends GenericLifecycleObserver>) Class.forName(
+            Package aPackage = klass.getPackage();
+            String name = klass.getCanonicalName();
+            final String fullPackage = aPackage != null ? aPackage.getName() : "";
+            final String adapterName = getAdapterName(fullPackage.isEmpty() ? name :
+                    name.substring(fullPackage.length() + 1));
+
+            @SuppressWarnings("unchecked") final Class<? extends GeneratedAdapter> aClass =
+                    (Class<? extends GeneratedAdapter>) Class.forName(
                             fullPackage.isEmpty() ? adapterName : fullPackage + "." + adapterName);
-            return aClass.getDeclaredConstructor(klass);
-        } catch (ClassNotFoundException e) {
-            final Class<?> superclass = klass.getSuperclass();
-            if (superclass != null) {
-                return getGeneratedAdapterConstructor(superclass);
+            Constructor<? extends GeneratedAdapter> constructor =
+                    aClass.getDeclaredConstructor(klass);
+            if (!constructor.isAccessible()) {
+                constructor.setAccessible(true);
             }
+            return constructor;
+        } catch (ClassNotFoundException e) {
+            return null;
         } catch (NoSuchMethodException e) {
             // this should not happen
             throw new RuntimeException(e);
         }
-        return null;
     }
 
-    static String getAdapterName(String className) {
+    private static int getObserverConstructorType(Class<?> klass) {
+        if (sCallbackCache.containsKey(klass)) {
+            return sCallbackCache.get(klass);
+        }
+        int type = resolveObserverCallbackType(klass);
+        sCallbackCache.put(klass, type);
+        return type;
+    }
+
+    private static int resolveObserverCallbackType(Class<?> klass) {
+        // anonymous class bug:35073837
+        if (klass.getCanonicalName() == null) {
+            return REFLECTIVE_CALLBACK;
+        }
+
+        Constructor<? extends GeneratedAdapter> constructor = generatedConstructor(klass);
+        if (constructor != null) {
+            sClassToAdapters.put(klass, Collections
+                    .<Constructor<? extends GeneratedAdapter>>singletonList(constructor));
+            return GENERATED_CALLBACK;
+        }
+
+        boolean hasLifecycleMethods = ClassesInfoCache.sInstance.hasLifecycleMethods(klass);
+        if (hasLifecycleMethods) {
+            return REFLECTIVE_CALLBACK;
+        }
+
+        Class<?> superclass = klass.getSuperclass();
+        List<Constructor<? extends GeneratedAdapter>> adapterConstructors = null;
+        if (isLifecycleParent(superclass)) {
+            if (getObserverConstructorType(superclass) == REFLECTIVE_CALLBACK) {
+                return REFLECTIVE_CALLBACK;
+            }
+            adapterConstructors = new ArrayList<>(sClassToAdapters.get(superclass));
+        }
+
+        for (Class<?> intrface : klass.getInterfaces()) {
+            if (!isLifecycleParent(intrface)) {
+                continue;
+            }
+            if (getObserverConstructorType(intrface) == REFLECTIVE_CALLBACK) {
+                return REFLECTIVE_CALLBACK;
+            }
+            if (adapterConstructors == null) {
+                adapterConstructors = new ArrayList<>();
+            }
+            adapterConstructors.addAll(sClassToAdapters.get(intrface));
+        }
+        if (adapterConstructors != null) {
+            sClassToAdapters.put(klass, adapterConstructors);
+            return GENERATED_CALLBACK;
+        }
+
+        return REFLECTIVE_CALLBACK;
+    }
+
+    private static boolean isLifecycleParent(Class<?> klass) {
+        return klass != null && LifecycleObserver.class.isAssignableFrom(klass);
+    }
+
+    /**
+     * Create a name for an adapter class.
+     */
+    public static String getAdapterName(String className) {
         return className.replace(".", "_") + "_LifecycleAdapter";
     }
 }
diff --git a/android/arch/lifecycle/LifecyclingTest.java b/android/arch/lifecycle/LifecyclingTest.java
new file mode 100644
index 0000000..70ce84c
--- /dev/null
+++ b/android/arch/lifecycle/LifecyclingTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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.instanceOf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.arch.lifecycle.observers.DerivedSequence1;
+import android.arch.lifecycle.observers.DerivedSequence2;
+import android.arch.lifecycle.observers.DerivedWithNewMethods;
+import android.arch.lifecycle.observers.DerivedWithNoNewMethods;
+import android.arch.lifecycle.observers.DerivedWithOverridenMethodsWithLfAnnotation;
+import android.arch.lifecycle.observers.InterfaceImpl1;
+import android.arch.lifecycle.observers.InterfaceImpl2;
+import android.arch.lifecycle.observers.InterfaceImpl3;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class LifecyclingTest {
+
+    @Test
+    public void testDerivedWithNewLfMethodsNoGeneratedAdapter() {
+        GenericLifecycleObserver callback = Lifecycling.getCallback(new DerivedWithNewMethods());
+        assertThat(callback, instanceOf(ReflectiveGenericLifecycleObserver.class));
+    }
+
+    @Test
+    public void testDerivedWithNoNewLfMethodsNoGeneratedAdapter() {
+        GenericLifecycleObserver callback = Lifecycling.getCallback(new DerivedWithNoNewMethods());
+        assertThat(callback, instanceOf(SingleGeneratedAdapterObserver.class));
+    }
+
+    @Test
+    public void testDerivedWithOverridenMethodsNoGeneratedAdapter() {
+        GenericLifecycleObserver callback = Lifecycling.getCallback(
+                new DerivedWithOverridenMethodsWithLfAnnotation());
+        // that is not effective but...
+        assertThat(callback, instanceOf(ReflectiveGenericLifecycleObserver.class));
+    }
+
+    @Test
+    public void testInterfaceImpl1NoGeneratedAdapter() {
+        GenericLifecycleObserver callback = Lifecycling.getCallback(new InterfaceImpl1());
+        assertThat(callback, instanceOf(SingleGeneratedAdapterObserver.class));
+    }
+
+    @Test
+    public void testInterfaceImpl2NoGeneratedAdapter() {
+        GenericLifecycleObserver callback = Lifecycling.getCallback(new InterfaceImpl2());
+        assertThat(callback, instanceOf(CompositeGeneratedAdaptersObserver.class));
+    }
+
+    @Test
+    public void testInterfaceImpl3NoGeneratedAdapter() {
+        GenericLifecycleObserver callback = Lifecycling.getCallback(new InterfaceImpl3());
+        assertThat(callback, instanceOf(CompositeGeneratedAdaptersObserver.class));
+    }
+
+    @Test
+    public void testDerivedSequence() {
+        GenericLifecycleObserver callback2 = Lifecycling.getCallback(new DerivedSequence2());
+        assertThat(callback2, instanceOf(ReflectiveGenericLifecycleObserver.class));
+        GenericLifecycleObserver callback1 = Lifecycling.getCallback(new DerivedSequence1());
+        assertThat(callback1, instanceOf(SingleGeneratedAdapterObserver.class));
+    }
+}
diff --git a/android/arch/lifecycle/LiveData.java b/android/arch/lifecycle/LiveData.java
index 99d859c..3aea6ac 100644
--- a/android/arch/lifecycle/LiveData.java
+++ b/android/arch/lifecycle/LiveData.java
@@ -1,411 +1,4 @@
-/*
- * 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.
- */
-
+//LiveData interface for tests
 package android.arch.lifecycle;
-
-import static android.arch.lifecycle.Lifecycle.State.DESTROYED;
-import static android.arch.lifecycle.Lifecycle.State.STARTED;
-
-import android.arch.core.executor.AppToolkitTaskExecutor;
-import android.arch.core.internal.SafeIterableMap;
-import android.arch.lifecycle.Lifecycle.State;
-import android.support.annotation.MainThread;
-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 hold 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())) {
-            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(LifecycleOwner owner, 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);
-        wrapper.activeStateChanged(isActiveState(owner.getLifecycle().getCurrentState()));
-    }
-
-    /**
-     * 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(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(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(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;
-        }
-        AppToolkitTaskExecutor.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 LifecycleObserver {
-        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;
-        }
-
-        @SuppressWarnings("unused")
-        @OnLifecycleEvent(Lifecycle.Event.ON_ANY)
-        void onStateChange() {
-            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 (!AppToolkitTaskExecutor.getInstance().isMainThread()) {
-            throw new IllegalStateException("Cannot invoke " + methodName + " on a background"
-                    + " thread");
-        }
-    }
+public class LiveData<T> {
 }
diff --git a/android/arch/lifecycle/LiveDataReactiveStreams.java b/android/arch/lifecycle/LiveDataReactiveStreams.java
index 0be0149..2b25bc9 100644
--- a/android/arch/lifecycle/LiveDataReactiveStreams.java
+++ b/android/arch/lifecycle/LiveDataReactiveStreams.java
@@ -16,7 +16,8 @@
 
 package android.arch.lifecycle;
 
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
+import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 
 import org.reactivestreams.Publisher;
@@ -85,7 +86,7 @@
                         if (n < 0 || mCanceled) {
                             return;
                         }
-                        AppToolkitTaskExecutor.getInstance().executeOnMainThread(new Runnable() {
+                        ArchTaskExecutor.getInstance().executeOnMainThread(new Runnable() {
                             @Override
                             public void run() {
                                 if (mCanceled) {
@@ -110,7 +111,7 @@
                         if (mCanceled) {
                             return;
                         }
-                        AppToolkitTaskExecutor.getInstance().executeOnMainThread(new Runnable() {
+                        ArchTaskExecutor.getInstance().executeOnMainThread(new Runnable() {
                             @Override
                             public void run() {
                                 if (mCanceled) {
@@ -133,40 +134,101 @@
 
     /**
      * Creates an Observable {@link LiveData} stream from a ReactiveStreams publisher.
+     *
+     * <p>
+     * When the LiveData becomes active, it subscribes to the emissions from the Publisher.
+     *
+     * <p>
+     * When the LiveData becomes inactive, the subscription is cleared.
+     * LiveData holds the last value emitted by the Publisher when the LiveData was active.
+     * <p>
+     * Therefore, in the case of a hot RxJava Observable, when a new LiveData {@link Observer} is
+     * added, it will automatically notify with the last value held in LiveData,
+     * which might not be the last value emitted by the Publisher.
+     *
+     * @param <T> The type of data hold by this instance.
      */
     public static <T> LiveData<T> fromPublisher(final Publisher<T> publisher) {
-        MutableLiveData<T> liveData = new MutableLiveData<>();
-        // Since we don't have a way to directly observe cancels, weakly hold the live data.
-        final WeakReference<MutableLiveData<T>> liveDataRef = new WeakReference<>(liveData);
-
-        publisher.subscribe(new Subscriber<T>() {
-            @Override
-            public void onSubscribe(Subscription s) {
-                // Don't worry about backpressure. If the stream is too noisy then backpressure can
-                // be handled upstream.
-                s.request(Long.MAX_VALUE);
-            }
-
-            @Override
-            public void onNext(final T t) {
-                final LiveData<T> liveData = liveDataRef.get();
-                if (liveData != null) {
-                    liveData.postValue(t);
-                }
-            }
-
-            @Override
-            public void onError(Throwable t) {
-                // Errors should be handled upstream, so propagate as a crash.
-                throw new RuntimeException(t);
-            }
-
-            @Override
-            public void onComplete() {
-            }
-        });
-
-        return liveData;
+        return new PublisherLiveData<>(publisher);
     }
 
+    /**
+     * Defines a {@link LiveData} object that wraps a {@link Publisher}.
+     *
+     * <p>
+     * When the LiveData becomes active, it subscribes to the emissions from the Publisher.
+     *
+     * <p>
+     * When the LiveData becomes inactive, the subscription is cleared.
+     * LiveData holds the last value emitted by the Publisher when the LiveData was active.
+     * <p>
+     * Therefore, in the case of a hot RxJava Observable, when a new LiveData {@link Observer} is
+     * added, it will automatically notify with the last value held in LiveData,
+     * which might not be the last value emitted by the Publisher.
+     *
+     * @param <T> The type of data hold by this instance.
+     */
+    private static class PublisherLiveData<T> extends LiveData<T> {
+        private WeakReference<Subscription> mSubscriptionRef;
+        private final Publisher mPublisher;
+        private final Object mLock = new Object();
+
+        PublisherLiveData(@NonNull final Publisher publisher) {
+            mPublisher = publisher;
+        }
+
+        @Override
+        protected void onActive() {
+            super.onActive();
+
+            mPublisher.subscribe(new Subscriber<T>() {
+                @Override
+                public void onSubscribe(Subscription s) {
+                    // Don't worry about backpressure. If the stream is too noisy then
+                    // backpressure can be handled upstream.
+                    synchronized (mLock) {
+                        s.request(Long.MAX_VALUE);
+                        mSubscriptionRef = new WeakReference<>(s);
+                    }
+                }
+
+                @Override
+                public void onNext(final T t) {
+                    postValue(t);
+                }
+
+                @Override
+                public void onError(Throwable t) {
+                    synchronized (mLock) {
+                        mSubscriptionRef = null;
+                    }
+                    // Errors should be handled upstream, so propagate as a crash.
+                    throw new RuntimeException(t);
+                }
+
+                @Override
+                public void onComplete() {
+                    synchronized (mLock) {
+                        mSubscriptionRef = null;
+                    }
+                }
+            });
+
+        }
+
+        @Override
+        protected void onInactive() {
+            super.onInactive();
+            synchronized (mLock) {
+                WeakReference<Subscription> subscriptionRef = mSubscriptionRef;
+                if (subscriptionRef != null) {
+                    Subscription subscription = subscriptionRef.get();
+                    if (subscription != null) {
+                        subscription.cancel();
+                    }
+                    mSubscriptionRef = null;
+                }
+            }
+        }
+    }
 }
diff --git a/android/arch/lifecycle/LiveDataReactiveStreamsTest.java b/android/arch/lifecycle/LiveDataReactiveStreamsTest.java
index 87fba27..7278847 100644
--- a/android/arch/lifecycle/LiveDataReactiveStreamsTest.java
+++ b/android/arch/lifecycle/LiveDataReactiveStreamsTest.java
@@ -16,12 +16,10 @@
 
 package android.arch.lifecycle;
 
-import static android.arch.lifecycle.Lifecycle.State.RESUMED;
-
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
 
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
 import android.arch.core.executor.TaskExecutor;
 import android.support.annotation.Nullable;
 import android.support.test.filters.SmallTest;
@@ -47,28 +45,7 @@
 
 @SmallTest
 public class LiveDataReactiveStreamsTest {
-    private static final Lifecycle sLifecycle = new Lifecycle() {
-        @Override
-        public void addObserver(LifecycleObserver observer) {
-        }
-
-        @Override
-        public void removeObserver(LifecycleObserver observer) {
-        }
-
-        @Override
-        public State getCurrentState() {
-            return RESUMED;
-        }
-    };
-    private static final LifecycleOwner S_LIFECYCLE_OWNER = new LifecycleOwner() {
-
-        @Override
-        public Lifecycle getLifecycle() {
-            return sLifecycle;
-        }
-
-    };
+    private LifecycleOwner mLifecycleOwner;
 
     private final List<String> mLiveDataOutput = new ArrayList<>();
     private final Observer<String> mObserver = new Observer<String>() {
@@ -85,8 +62,19 @@
 
     @Before
     public void init() {
+        mLifecycleOwner = new LifecycleOwner() {
+            LifecycleRegistry mRegistry = new LifecycleRegistry(this);
+            {
+                mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
+            }
+
+            @Override
+            public Lifecycle getLifecycle() {
+                return mRegistry;
+            }
+        };
         mTestThread = Thread.currentThread();
-        AppToolkitTaskExecutor.getInstance().setDelegate(new TaskExecutor() {
+        ArchTaskExecutor.getInstance().setDelegate(new TaskExecutor() {
 
             @Override
             public void executeOnDiskIO(Runnable runnable) {
@@ -109,7 +97,7 @@
 
     @After
     public void removeExecutorDelegate() {
-        AppToolkitTaskExecutor.getInstance().setDelegate(null);
+        ArchTaskExecutor.getInstance().setDelegate(null);
     }
 
     @Test
@@ -117,7 +105,7 @@
         PublishProcessor<String> processor = PublishProcessor.create();
         LiveData<String> liveData = LiveDataReactiveStreams.fromPublisher(processor);
 
-        liveData.observe(S_LIFECYCLE_OWNER, mObserver);
+        liveData.observe(mLifecycleOwner, mObserver);
 
         processor.onNext("foo");
         processor.onNext("bar");
@@ -132,13 +120,13 @@
         PublishProcessor<String> processor = PublishProcessor.create();
         LiveData<String> liveData = LiveDataReactiveStreams.fromPublisher(processor);
 
-        liveData.observe(S_LIFECYCLE_OWNER, mObserver);
+        liveData.observe(mLifecycleOwner, mObserver);
 
         processor.onNext("foo");
         processor.onNext("bar");
 
         // The second mObserver should only get the newest value and any later values.
-        liveData.observe(S_LIFECYCLE_OWNER, new Observer<String>() {
+        liveData.observe(mLifecycleOwner, new Observer<String>() {
             @Override
             public void onChanged(@Nullable String s) {
                 output2.add(s);
@@ -152,12 +140,44 @@
     }
 
     @Test
+    public void convertsFromPublisherAfterInactive() {
+        PublishProcessor<String> processor = PublishProcessor.create();
+        LiveData<String> liveData = LiveDataReactiveStreams.fromPublisher(processor);
+
+        liveData.observe(mLifecycleOwner, mObserver);
+        processor.onNext("foo");
+        liveData.removeObserver(mObserver);
+        processor.onNext("bar");
+
+        liveData.observe(mLifecycleOwner, mObserver);
+        processor.onNext("baz");
+
+        assertThat(mLiveDataOutput, is(Arrays.asList("foo", "foo", "baz")));
+    }
+
+    @Test
+    public void convertsFromPublisherManagesSubcriptions() {
+        PublishProcessor<String> processor = PublishProcessor.create();
+        LiveData<String> liveData = LiveDataReactiveStreams.fromPublisher(processor);
+
+        assertThat(processor.hasSubscribers(), is(false));
+        liveData.observe(mLifecycleOwner, mObserver);
+
+        // once the live data is active, there's a subscriber
+        assertThat(processor.hasSubscribers(), is(true));
+
+        liveData.removeObserver(mObserver);
+        // once the live data is inactive, the subscriber is removed
+        assertThat(processor.hasSubscribers(), is(false));
+    }
+
+    @Test
     public void convertsFromAsyncPublisher() {
         Flowable<String> input = Flowable.just("foo")
                 .concatWith(Flowable.just("bar", "baz").observeOn(sBackgroundScheduler));
         LiveData<String> liveData = LiveDataReactiveStreams.fromPublisher(input);
 
-        liveData.observe(S_LIFECYCLE_OWNER, mObserver);
+        liveData.observe(mLifecycleOwner, mObserver);
 
         assertThat(mLiveDataOutput, is(Collections.singletonList("foo")));
         sBackgroundScheduler.triggerActions();
@@ -170,7 +190,7 @@
         liveData.setValue("foo");
         assertThat(liveData.getValue(), is("foo"));
 
-        Flowable.fromPublisher(LiveDataReactiveStreams.toPublisher(S_LIFECYCLE_OWNER, liveData))
+        Flowable.fromPublisher(LiveDataReactiveStreams.toPublisher(mLifecycleOwner, liveData))
                 .subscribe(mOutputProcessor);
 
         liveData.setValue("bar");
@@ -188,7 +208,7 @@
         assertThat(liveData.getValue(), is("foo"));
 
         Disposable disposable = Flowable
-                .fromPublisher(LiveDataReactiveStreams.toPublisher(S_LIFECYCLE_OWNER, liveData))
+                .fromPublisher(LiveDataReactiveStreams.toPublisher(mLifecycleOwner, liveData))
                 .subscribe(new Consumer<String>() {
                     @Override
                     public void accept(String s) throws Exception {
@@ -216,7 +236,7 @@
 
         final AsyncSubject<Subscription> subscriptionSubject = AsyncSubject.create();
 
-        Flowable.fromPublisher(LiveDataReactiveStreams.toPublisher(S_LIFECYCLE_OWNER, liveData))
+        Flowable.fromPublisher(LiveDataReactiveStreams.toPublisher(mLifecycleOwner, liveData))
                 .subscribe(new Subscriber<String>() {
                     @Override
                     public void onSubscribe(Subscription s) {
@@ -275,7 +295,7 @@
     public void convertsToPublisherWithAsyncData() {
         MutableLiveData<String> liveData = new MutableLiveData<>();
 
-        Flowable.fromPublisher(LiveDataReactiveStreams.toPublisher(S_LIFECYCLE_OWNER, liveData))
+        Flowable.fromPublisher(LiveDataReactiveStreams.toPublisher(mLifecycleOwner, liveData))
                 .observeOn(sBackgroundScheduler)
                 .subscribe(mOutputProcessor);
 
diff --git a/android/arch/lifecycle/LiveDataTest.java b/android/arch/lifecycle/LiveDataTest.java
index ed2a35d..9f0b425 100644
--- a/android/arch/lifecycle/LiveDataTest.java
+++ b/android/arch/lifecycle/LiveDataTest.java
@@ -36,16 +36,20 @@
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
 import android.arch.lifecycle.util.InstantTaskExecutor;
 import android.support.annotation.Nullable;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.InOrder;
 import org.mockito.Mockito;
 
 @SuppressWarnings({"unchecked"})
+@RunWith(JUnit4.class)
 public class LiveDataTest {
     private PublicLiveData<String> mLiveData;
     private LifecycleOwner mOwner;
@@ -66,12 +70,12 @@
 
     @Before
     public void swapExecutorDelegate() {
-        AppToolkitTaskExecutor.getInstance().setDelegate(new InstantTaskExecutor());
+        ArchTaskExecutor.getInstance().setDelegate(new InstantTaskExecutor());
     }
 
     @After
     public void removeExecutorDelegate() {
-        AppToolkitTaskExecutor.getInstance().setDelegate(null);
+        ArchTaskExecutor.getInstance().setDelegate(null);
     }
 
     @Test
@@ -418,6 +422,40 @@
         verify(mActiveObserversChanged, never()).onCall(anyBoolean());
     }
 
+    @Test
+    public void testRemoveDuringAddition() {
+        mRegistry.handleLifecycleEvent(ON_START);
+        mLiveData.setValue("bla");
+        mLiveData.observeForever(new Observer<String>() {
+            @Override
+            public void onChanged(@Nullable String s) {
+                mLiveData.removeObserver(this);
+            }
+        });
+        assertThat(mLiveData.hasActiveObservers(), is(false));
+        InOrder inOrder = Mockito.inOrder(mActiveObserversChanged);
+        inOrder.verify(mActiveObserversChanged).onCall(true);
+        inOrder.verify(mActiveObserversChanged).onCall(false);
+        inOrder.verifyNoMoreInteractions();
+    }
+
+    @Test
+    public void testRemoveDuringBringingUpToState() {
+        mLiveData.setValue("bla");
+        mLiveData.observeForever(new Observer<String>() {
+            @Override
+            public void onChanged(@Nullable String s) {
+                mLiveData.removeObserver(this);
+            }
+        });
+        mRegistry.handleLifecycleEvent(ON_RESUME);
+        assertThat(mLiveData.hasActiveObservers(), is(false));
+        InOrder inOrder = Mockito.inOrder(mActiveObserversChanged);
+        inOrder.verify(mActiveObserversChanged).onCall(true);
+        inOrder.verify(mActiveObserversChanged).onCall(false);
+        inOrder.verifyNoMoreInteractions();
+    }
+
     @SuppressWarnings("WeakerAccess")
     static class PublicLiveData<T> extends LiveData<T> {
         // cannot spy due to internal calls
diff --git a/android/arch/lifecycle/MediatorLiveDataTest.java b/android/arch/lifecycle/MediatorLiveDataTest.java
index 3de3eee..e2eadbe 100644
--- a/android/arch/lifecycle/MediatorLiveDataTest.java
+++ b/android/arch/lifecycle/MediatorLiveDataTest.java
@@ -25,7 +25,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
 import android.arch.lifecycle.util.InstantTaskExecutor;
 import android.support.annotation.Nullable;
 
@@ -69,7 +69,7 @@
 
     @Before
     public void swapExecutorDelegate() {
-        AppToolkitTaskExecutor.getInstance().setDelegate(new InstantTaskExecutor());
+        ArchTaskExecutor.getInstance().setDelegate(new InstantTaskExecutor());
     }
 
     @Test
diff --git a/android/arch/lifecycle/MethodCallsLogger.java b/android/arch/lifecycle/MethodCallsLogger.java
new file mode 100644
index 0000000..031e43e
--- /dev/null
+++ b/android/arch/lifecycle/MethodCallsLogger.java
@@ -0,0 +1,42 @@
+/*
+ * 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.support.annotation.RestrictTo;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class MethodCallsLogger {
+    private Map<String, Integer> mCalledMethods = new HashMap<>();
+
+    /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public boolean approveCall(String name, int type) {
+        Integer nullableMask = mCalledMethods.get(name);
+        int mask = nullableMask != null ? nullableMask : 0;
+        boolean wasCalled = (mask & type) != 0;
+        mCalledMethods.put(name, mask | type);
+        return !wasCalled;
+    }
+}
diff --git a/android/arch/lifecycle/ProcessOwnerTest.java b/android/arch/lifecycle/ProcessOwnerTest.java
index e80e11c..37bdcdb 100644
--- a/android/arch/lifecycle/ProcessOwnerTest.java
+++ b/android/arch/lifecycle/ProcessOwnerTest.java
@@ -37,6 +37,7 @@
 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.After;
 import org.junit.Rule;
@@ -78,7 +79,7 @@
 
     @Test
     public void testNavigation() throws Throwable {
-        LifecycleActivity firstActivity = setupObserverOnResume();
+        FragmentActivity firstActivity = setupObserverOnResume();
         Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor(
                 NavigationTestActivitySecond.class.getCanonicalName(), null, false);
         Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
@@ -88,15 +89,15 @@
         firstActivity.finish();
         firstActivity.startActivity(intent);
 
-        LifecycleActivity secondActivity = (LifecycleActivity) monitor.waitForActivity();
+        FragmentActivity secondActivity = (FragmentActivity) monitor.waitForActivity();
         assertThat("Failed to navigate", secondActivity, notNullValue());
         checkProcessObserverSilent(secondActivity);
     }
 
     @Test
     public void testRecreation() throws Throwable {
-        LifecycleActivity activity = setupObserverOnResume();
-        LifecycleActivity recreated = TestUtils.recreateActivity(activity, activityTestRule);
+        FragmentActivity activity = setupObserverOnResume();
+        FragmentActivity recreated = TestUtils.recreateActivity(activity, activityTestRule);
         assertThat("Failed to recreate", recreated, notNullValue());
         checkProcessObserverSilent(recreated);
     }
@@ -112,14 +113,15 @@
 
         NavigationTestActivityFirst activity = activityTestRule.getActivity();
         activity.startActivity(new Intent(activity, NavigationDialogActivity.class));
-        LifecycleActivity dialogActivity = (LifecycleActivity) monitor.waitForActivity();
+        FragmentActivity dialogActivity = (FragmentActivity) monitor.waitForActivity();
         checkProcessObserverSilent(dialogActivity);
 
         List<Event> events = Collections.synchronizedList(new ArrayList<>());
 
         LifecycleObserver collectingObserver = new LifecycleObserver() {
             @OnLifecycleEvent(Event.ON_ANY)
-            public void onStateChanged(LifecycleOwner provider, Event event) {
+            public void onStateChanged(@SuppressWarnings("unused") LifecycleOwner provider,
+                    Event event) {
                 events.add(event);
             }
         };
@@ -138,8 +140,8 @@
         dialogActivity.finish();
     }
 
-    private LifecycleActivity setupObserverOnResume() throws Throwable {
-        LifecycleActivity firstActivity = activityTestRule.getActivity();
+    private FragmentActivity setupObserverOnResume() throws Throwable {
+        FragmentActivity firstActivity = activityTestRule.getActivity();
         waitTillResumed(firstActivity, activityTestRule);
         addProcessObserver(mObserver);
         mObserver.mChangedState = false;
@@ -156,7 +158,7 @@
                 ProcessLifecycleOwner.get().getLifecycle().removeObserver(observer));
     }
 
-    private void checkProcessObserverSilent(LifecycleActivity activity) throws Throwable {
+    private void checkProcessObserverSilent(FragmentActivity activity) throws Throwable {
         waitTillResumed(activity, activityTestRule);
         assertThat(mObserver.mChangedState, is(false));
         activityTestRule.runOnUiThread(() ->
diff --git a/android/arch/lifecycle/ReflectiveGenericLifecycleObserver.java b/android/arch/lifecycle/ReflectiveGenericLifecycleObserver.java
index 44815e6..f010ed8 100644
--- a/android/arch/lifecycle/ReflectiveGenericLifecycleObserver.java
+++ b/android/arch/lifecycle/ReflectiveGenericLifecycleObserver.java
@@ -16,204 +16,23 @@
 
 package android.arch.lifecycle;
 
+import android.arch.lifecycle.ClassesInfoCache.CallbackInfo;
 import android.arch.lifecycle.Lifecycle.Event;
 
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-
 /**
  * An internal implementation of {@link GenericLifecycleObserver} that relies on reflection.
  */
 class ReflectiveGenericLifecycleObserver implements GenericLifecycleObserver {
     private final Object mWrapped;
     private final CallbackInfo mInfo;
-    @SuppressWarnings("WeakerAccess")
-    static final Map<Class, CallbackInfo> sInfoCache = new HashMap<>();
 
     ReflectiveGenericLifecycleObserver(Object wrapped) {
         mWrapped = wrapped;
-        mInfo = getInfo(mWrapped.getClass());
+        mInfo = ClassesInfoCache.sInstance.getInfo(mWrapped.getClass());
     }
 
     @Override
     public void onStateChanged(LifecycleOwner source, Event event) {
-        invokeCallbacks(mInfo, source, event);
+        mInfo.invokeCallbacks(source, event, mWrapped);
     }
-
-    private void invokeMethodsForEvent(List<MethodReference> handlers, LifecycleOwner source,
-            Event event) {
-        if (handlers != null) {
-            for (int i = handlers.size() - 1; i >= 0; i--) {
-                MethodReference reference = handlers.get(i);
-                invokeCallback(reference, source, event);
-            }
-        }
-    }
-
-    @SuppressWarnings("ConstantConditions")
-    private void invokeCallbacks(CallbackInfo info, LifecycleOwner source, Event event) {
-        invokeMethodsForEvent(info.mEventToHandlers.get(event), source, event);
-        invokeMethodsForEvent(info.mEventToHandlers.get(Event.ON_ANY), source, event);
-    }
-
-    private void invokeCallback(MethodReference reference, LifecycleOwner source, Event event) {
-        //noinspection TryWithIdenticalCatches
-        try {
-            switch (reference.mCallType) {
-                case CALL_TYPE_NO_ARG:
-                    reference.mMethod.invoke(mWrapped);
-                    break;
-                case CALL_TYPE_PROVIDER:
-                    reference.mMethod.invoke(mWrapped, source);
-                    break;
-                case CALL_TYPE_PROVIDER_WITH_EVENT:
-                    reference.mMethod.invoke(mWrapped, source, event);
-                    break;
-            }
-        } catch (InvocationTargetException e) {
-            throw new RuntimeException("Failed to call observer method", e.getCause());
-        } catch (IllegalAccessException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    private static CallbackInfo getInfo(Class klass) {
-        CallbackInfo existing = sInfoCache.get(klass);
-        if (existing != null) {
-            return existing;
-        }
-        existing = createInfo(klass);
-        return existing;
-    }
-
-    private static void verifyAndPutHandler(Map<MethodReference, Event> handlers,
-            MethodReference newHandler, Event newEvent, Class klass) {
-        Event event = handlers.get(newHandler);
-        if (event != null && newEvent != event) {
-            Method method = newHandler.mMethod;
-            throw new IllegalArgumentException(
-                    "Method " + method.getName() + " in " + klass.getName()
-                            + " already declared with different @OnLifecycleEvent value: previous"
-                            + " value " + event + ", new value " + newEvent);
-        }
-        if (event == null) {
-            handlers.put(newHandler, newEvent);
-        }
-    }
-
-    private static CallbackInfo createInfo(Class klass) {
-        Class superclass = klass.getSuperclass();
-        Map<MethodReference, Event> handlerToEvent = new HashMap<>();
-        if (superclass != null) {
-            CallbackInfo superInfo = getInfo(superclass);
-            if (superInfo != null) {
-                handlerToEvent.putAll(superInfo.mHandlerToEvent);
-            }
-        }
-
-        Method[] methods = klass.getDeclaredMethods();
-
-        Class[] interfaces = klass.getInterfaces();
-        for (Class intrfc : interfaces) {
-            for (Entry<MethodReference, Event> entry : getInfo(intrfc).mHandlerToEvent.entrySet()) {
-                verifyAndPutHandler(handlerToEvent, entry.getKey(), entry.getValue(), klass);
-            }
-        }
-
-        for (Method method : methods) {
-            OnLifecycleEvent annotation = method.getAnnotation(OnLifecycleEvent.class);
-            if (annotation == null) {
-                continue;
-            }
-            Class<?>[] params = method.getParameterTypes();
-            int callType = CALL_TYPE_NO_ARG;
-            if (params.length > 0) {
-                callType = CALL_TYPE_PROVIDER;
-                if (!params[0].isAssignableFrom(LifecycleOwner.class)) {
-                    throw new IllegalArgumentException(
-                            "invalid parameter type. Must be one and instanceof LifecycleOwner");
-                }
-            }
-            Event event = annotation.value();
-
-            if (params.length > 1) {
-                callType = CALL_TYPE_PROVIDER_WITH_EVENT;
-                if (!params[1].isAssignableFrom(Event.class)) {
-                    throw new IllegalArgumentException(
-                            "invalid parameter type. second arg must be an event");
-                }
-                if (event != Event.ON_ANY) {
-                    throw new IllegalArgumentException(
-                            "Second arg is supported only for ON_ANY value");
-                }
-            }
-            if (params.length > 2) {
-                throw new IllegalArgumentException("cannot have more than 2 params");
-            }
-            MethodReference methodReference = new MethodReference(callType, method);
-            verifyAndPutHandler(handlerToEvent, methodReference, event, klass);
-        }
-        CallbackInfo info = new CallbackInfo(handlerToEvent);
-        sInfoCache.put(klass, info);
-        return info;
-    }
-
-    @SuppressWarnings("WeakerAccess")
-    static class CallbackInfo {
-        final Map<Event, List<MethodReference>> mEventToHandlers;
-        final Map<MethodReference, Event> mHandlerToEvent;
-
-        CallbackInfo(Map<MethodReference, Event> handlerToEvent) {
-            mHandlerToEvent = handlerToEvent;
-            mEventToHandlers = new HashMap<>();
-            for (Entry<MethodReference, Event> entry : handlerToEvent.entrySet()) {
-                Event event = entry.getValue();
-                List<MethodReference> methodReferences = mEventToHandlers.get(event);
-                if (methodReferences == null) {
-                    methodReferences = new ArrayList<>();
-                    mEventToHandlers.put(event, methodReferences);
-                }
-                methodReferences.add(entry.getKey());
-            }
-        }
-    }
-
-    @SuppressWarnings("WeakerAccess")
-    static class MethodReference {
-        final int mCallType;
-        final Method mMethod;
-
-        MethodReference(int callType, Method method) {
-            mCallType = callType;
-            mMethod = method;
-            mMethod.setAccessible(true);
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) {
-                return true;
-            }
-            if (o == null || getClass() != o.getClass()) {
-                return false;
-            }
-
-            MethodReference that = (MethodReference) o;
-            return mCallType == that.mCallType && mMethod.getName().equals(that.mMethod.getName());
-        }
-
-        @Override
-        public int hashCode() {
-            return 31 * mCallType + mMethod.getName().hashCode();
-        }
-    }
-
-    private static final int CALL_TYPE_NO_ARG = 0;
-    private static final int CALL_TYPE_PROVIDER = 1;
-    private static final int CALL_TYPE_PROVIDER_WITH_EVENT = 2;
 }
diff --git a/android/arch/lifecycle/SingleGeneratedAdapterObserver.java b/android/arch/lifecycle/SingleGeneratedAdapterObserver.java
new file mode 100644
index 0000000..d176a3a
--- /dev/null
+++ b/android/arch/lifecycle/SingleGeneratedAdapterObserver.java
@@ -0,0 +1,38 @@
+/*
+ * 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.support.annotation.RestrictTo;
+
+/**
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class SingleGeneratedAdapterObserver implements GenericLifecycleObserver {
+
+    private final GeneratedAdapter mGeneratedAdapter;
+
+    SingleGeneratedAdapterObserver(GeneratedAdapter generatedAdapter) {
+        mGeneratedAdapter = generatedAdapter;
+    }
+
+    @Override
+    public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
+        mGeneratedAdapter.callMethods(source, event, false, null);
+        mGeneratedAdapter.callMethods(source, event, true, null);
+    }
+}
diff --git a/android/arch/lifecycle/TestUtils.java b/android/arch/lifecycle/TestUtils.java
index c5a520f..f0214bf 100644
--- a/android/arch/lifecycle/TestUtils.java
+++ b/android/arch/lifecycle/TestUtils.java
@@ -23,15 +23,16 @@
 import android.app.Instrumentation.ActivityMonitor;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.rule.ActivityTestRule;
+import android.support.v4.app.FragmentActivity;
 
 import java.util.concurrent.CountDownLatch;
 
-public class TestUtils {
+class TestUtils {
 
     private static final long TIMEOUT_MS = 2000;
 
     @SuppressWarnings("unchecked")
-    public static <T extends Activity> T recreateActivity(final T activity, ActivityTestRule rule)
+    static <T extends Activity> T recreateActivity(final T activity, ActivityTestRule rule)
             throws Throwable {
         ActivityMonitor monitor = new ActivityMonitor(
                 activity.getClass().getCanonicalName(), null, false);
@@ -60,7 +61,7 @@
         return result;
     }
 
-    static void waitTillResumed(final LifecycleActivity a, ActivityTestRule<?> activityRule)
+    static void waitTillResumed(final FragmentActivity a, ActivityTestRule<?> activityRule)
             throws Throwable {
         final CountDownLatch latch = new CountDownLatch(1);
         activityRule.runOnUiThread(() -> {
diff --git a/android/arch/lifecycle/Transformations.java b/android/arch/lifecycle/Transformations.java
index c316563..9ce9cbb 100644
--- a/android/arch/lifecycle/Transformations.java
+++ b/android/arch/lifecycle/Transformations.java
@@ -22,6 +22,12 @@
 
 /**
  * Transformations for a {@link LiveData} class.
+ * <p>
+ * You can use transformation methods to carry information across the observer's lifecycle. The
+ * transformations aren't calculated unless an observer is observing the returned LiveData object.
+ * <p>
+ * Because the transformations are calculated lazily, lifecycle-related behavior is implicitly
+ * passed down without requiring additional explicit calls or dependencies.
  */
 @SuppressWarnings("WeakerAccess")
 public class Transformations {
@@ -34,6 +40,18 @@
      * LiveData and returns LiveData, which emits resulting values.
      * <p>
      * The given function {@code func} will be executed on the main thread.
+     * <p>
+     * Suppose that you have a LiveData, named {@code userLiveData}, that contains user data and you
+     * need to display the user name, created by concatenating the first and the last
+     * name of the user. You can define a function that handles the name creation, that will be
+     * applied to every value emitted by {@code useLiveData}.
+     *
+     * <pre>
+     * LiveData<User> userLiveData = ...;
+     * LiveData<String> userName = Transformations.map(userLiveData, user -> {
+     *      return user.firstName + " " + user.lastName
+     * });
+     * </pre>
      *
      * @param source a {@code LiveData} to listen to
      * @param func   a function to apply
@@ -63,9 +81,39 @@
      * <p>
      * If the given function returns null, then {@code swLiveData} is not "backed" by any other
      * LiveData.
+     *
      * <p>
      * The given function {@code func} will be executed on the main thread.
      *
+     * <p>
+     * Consider the case where you have a LiveData containing a user id. Every time there's a new
+     * user id emitted, you want to trigger a request to get the user object corresponding to that
+     * id, from a repository that also returns a LiveData.
+     * <p>
+     * The {@code userIdLiveData} is the trigger and the LiveData returned by the {@code
+     * repository.getUserById} is the "backing" LiveData.
+     * <p>
+     * In a scenario where the repository contains User(1, "Jane") and User(2, "John"), when the
+     * userIdLiveData value is set to "1", the {@code switchMap} will call {@code getUser(1)},
+     * that will return a LiveData containing the value User(1, "Jane"). So now, the userLiveData
+     * will emit User(1, "Jane"). When the user in the repository gets updated to User(1, "Sarah"),
+     * the {@code userLiveData} gets automatically notified and will emit User(1, "Sarah").
+     * <p>
+     * When the {@code setUserId} method is called with userId = "2", the value of the {@code
+     * userIdLiveData} changes and automatically triggers a request for getting the user with id
+     * "2" from the repository. So, the {@code userLiveData} emits User(2, "John"). The LiveData
+     * returned by {@code repository.getUserById(1)} is removed as a source.
+     *
+     * <pre>
+     * MutableLiveData<String> userIdLiveData = ...;
+     * LiveData<User> userLiveData = Transformations.switchMap(userIdLiveData, id ->
+     *     repository.getUserById(id));
+     *
+     * void setUserId(String userId) {
+     *      this.userIdLiveData.setValue(userId);
+     * }
+     * </pre>
+     *
      * @param trigger a {@code LiveData} to listen to
      * @param func    a function which creates "backing" LiveData
      * @param <X>     a type of {@code source} LiveData
diff --git a/android/arch/lifecycle/TransformationsTest.java b/android/arch/lifecycle/TransformationsTest.java
index e92ecca..940a3e8 100644
--- a/android/arch/lifecycle/TransformationsTest.java
+++ b/android/arch/lifecycle/TransformationsTest.java
@@ -25,7 +25,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
 import android.arch.core.util.Function;
 import android.arch.lifecycle.util.InstantTaskExecutor;
 
@@ -42,7 +42,7 @@
 
     @Before
     public void swapExecutorDelegate() {
-        AppToolkitTaskExecutor.getInstance().setDelegate(new InstantTaskExecutor());
+        ArchTaskExecutor.getInstance().setDelegate(new InstantTaskExecutor());
     }
 
     @Before
diff --git a/android/arch/lifecycle/ViewModelProviderTest.java b/android/arch/lifecycle/ViewModelProviderTest.java
index 61760fc..8877357 100644
--- a/android/arch/lifecycle/ViewModelProviderTest.java
+++ b/android/arch/lifecycle/ViewModelProviderTest.java
@@ -21,6 +21,8 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import android.arch.lifecycle.ViewModelProvider.NewInstanceFactory;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
 
 import org.junit.Assert;
 import org.junit.Before;
@@ -82,6 +84,18 @@
         assertThat(viewModel, is(provider.get(ViewModel1.class)));
     }
 
+    @Test(expected = IllegalStateException.class)
+    public void testNotAttachedActivity() {
+        // This is similar to call ViewModelProviders.of in Activity's constructor
+        ViewModelProviders.of(new FragmentActivity());
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testNotAttachedFragment() {
+        // This is similar to call ViewModelProviders.of in Activity's constructor
+        ViewModelProviders.of(new Fragment());
+    }
+
     public static class ViewModel1 extends ViewModel {
         boolean mCleared;
 
diff --git a/android/arch/lifecycle/ViewModelProviders.java b/android/arch/lifecycle/ViewModelProviders.java
index f64365b..746162a 100644
--- a/android/arch/lifecycle/ViewModelProviders.java
+++ b/android/arch/lifecycle/ViewModelProviders.java
@@ -17,6 +17,7 @@
 package android.arch.lifecycle;
 
 import android.annotation.SuppressLint;
+import android.app.Activity;
 import android.app.Application;
 import android.arch.lifecycle.ViewModelProvider.Factory;
 import android.support.annotation.MainThread;
@@ -40,6 +41,23 @@
         }
     }
 
+    private static Application checkApplication(Activity activity) {
+        Application application = activity.getApplication();
+        if (application == null) {
+            throw new IllegalStateException("Your activity/fragment is not yet attached to "
+                    + "Application. You can't request ViewModel before onCreate call.");
+        }
+        return application;
+    }
+
+    private static Activity checkActivity(Fragment fragment) {
+        Activity activity = fragment.getActivity();
+        if (activity == null) {
+            throw new IllegalStateException("Can't create ViewModelProvider for detached fragment");
+        }
+        return activity;
+    }
+
     /**
      * Creates a {@link ViewModelProvider}, which retains ViewModels while a scope of given
      * {@code fragment} is alive. More detailed explanation is in {@link ViewModel}.
@@ -51,12 +69,7 @@
      */
     @MainThread
     public static ViewModelProvider of(@NonNull Fragment fragment) {
-        FragmentActivity activity = fragment.getActivity();
-        if (activity == null) {
-            throw new IllegalArgumentException(
-                    "Can't create ViewModelProvider for detached fragment");
-        }
-        initializeFactoryIfNeeded(activity.getApplication());
+        initializeFactoryIfNeeded(checkApplication(checkActivity(fragment)));
         return new ViewModelProvider(ViewModelStores.of(fragment), sDefaultFactory);
     }
 
@@ -71,7 +84,7 @@
      */
     @MainThread
     public static ViewModelProvider of(@NonNull FragmentActivity activity) {
-        initializeFactoryIfNeeded(activity.getApplication());
+        initializeFactoryIfNeeded(checkApplication(activity));
         return new ViewModelProvider(ViewModelStores.of(activity), sDefaultFactory);
     }
 
@@ -87,6 +100,7 @@
      */
     @MainThread
     public static ViewModelProvider of(@NonNull Fragment fragment, @NonNull Factory factory) {
+        checkApplication(checkActivity(fragment));
         return new ViewModelProvider(ViewModelStores.of(fragment), factory);
     }
 
@@ -103,6 +117,7 @@
     @MainThread
     public static ViewModelProvider of(@NonNull FragmentActivity activity,
             @NonNull Factory factory) {
+        checkApplication(activity);
         return new ViewModelProvider(ViewModelStores.of(activity), factory);
     }
 
diff --git a/android/arch/lifecycle/ViewModelTest.java b/android/arch/lifecycle/ViewModelTest.java
index 98ce027..03ebdf3 100644
--- a/android/arch/lifecycle/ViewModelTest.java
+++ b/android/arch/lifecycle/ViewModelTest.java
@@ -32,6 +32,7 @@
 import android.support.test.filters.MediumTest;
 import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.app.Fragment;
 import android.support.v4.app.FragmentActivity;
 import android.support.v4.app.FragmentManager;
 
@@ -120,7 +121,7 @@
             void onResume() {
                 try {
                     final FragmentManager manager = activity.getSupportFragmentManager();
-                    LifecycleFragment fragment = new LifecycleFragment();
+                    Fragment fragment = new Fragment();
                     manager.beginTransaction().add(fragment, "temp").commitNow();
                     ViewModel1 vm = ViewModelProviders.of(fragment).get(ViewModel1.class);
                     assertThat(vm.mCleared, is(false));
diff --git a/android/arch/lifecycle/activity/EmptyActivity.java b/android/arch/lifecycle/activity/EmptyActivity.java
index 017fff4..c32c898 100644
--- a/android/arch/lifecycle/activity/EmptyActivity.java
+++ b/android/arch/lifecycle/activity/EmptyActivity.java
@@ -16,13 +16,12 @@
 
 package android.arch.lifecycle.activity;
 
+import android.arch.lifecycle.extensions.test.R;
 import android.os.Bundle;
 import android.support.annotation.Nullable;
+import android.support.v4.app.FragmentActivity;
 
-import android.arch.lifecycle.LifecycleActivity;
-import android.arch.lifecycle.extensions.test.R;
-
-public class EmptyActivity extends LifecycleActivity {
+public class EmptyActivity extends FragmentActivity {
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
diff --git a/android/arch/lifecycle/activity/FragmentLifecycleActivity.java b/android/arch/lifecycle/activity/FragmentLifecycleActivity.java
index f4485e8..2eb1cc2 100644
--- a/android/arch/lifecycle/activity/FragmentLifecycleActivity.java
+++ b/android/arch/lifecycle/activity/FragmentLifecycleActivity.java
@@ -17,7 +17,6 @@
 package android.arch.lifecycle.activity;
 
 import android.arch.lifecycle.Lifecycle;
-import android.arch.lifecycle.LifecycleFragment;
 import android.arch.lifecycle.LifecycleObserver;
 import android.arch.lifecycle.LifecycleOwner;
 import android.arch.lifecycle.OnLifecycleEvent;
@@ -26,6 +25,7 @@
 import android.content.Intent;
 import android.os.Bundle;
 import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
 import android.support.v7.app.AppCompatActivity;
 
 import java.util.ArrayList;
@@ -70,9 +70,9 @@
         mLoggedEvents.clear();
     }
 
-    public static class MainFragment extends LifecycleFragment {
+    public static class MainFragment extends Fragment {
         @Nullable
-        LifecycleFragment mNestedFragment;
+        Fragment mNestedFragment;
 
         @Override
         public void onCreate(@Nullable Bundle savedInstanceState) {
@@ -85,7 +85,7 @@
         }
     }
 
-    public static class NestedFragment extends LifecycleFragment {
+    public static class NestedFragment extends Fragment {
     }
 
     public static Intent intentFor(Context context, boolean nested) {
@@ -98,7 +98,8 @@
         mObservedOwner = provider;
         provider.getLifecycle().addObserver(new LifecycleObserver() {
             @OnLifecycleEvent(Lifecycle.Event.ON_ANY)
-            public void anyEvent(LifecycleOwner owner, Lifecycle.Event event) {
+            public void anyEvent(@SuppressWarnings("unused") LifecycleOwner owner,
+                    Lifecycle.Event event) {
                 mLoggedEvents.add(event);
             }
         });
diff --git a/android/arch/lifecycle/observers/Base.java b/android/arch/lifecycle/observers/Base.java
new file mode 100644
index 0000000..08919d4
--- /dev/null
+++ b/android/arch/lifecycle/observers/Base.java
@@ -0,0 +1,28 @@
+/*
+ * 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.observers;
+
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LifecycleObserver;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+public class Base implements LifecycleObserver {
+
+    @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
+    public void onCreate() {
+    }
+}
diff --git a/android/arch/lifecycle/observers/Base_LifecycleAdapter.java b/android/arch/lifecycle/observers/Base_LifecycleAdapter.java
new file mode 100644
index 0000000..4218b96
--- /dev/null
+++ b/android/arch/lifecycle/observers/Base_LifecycleAdapter.java
@@ -0,0 +1,34 @@
+/*
+ * 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.observers;
+
+import android.arch.lifecycle.GeneratedAdapter;
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LifecycleOwner;
+import android.arch.lifecycle.MethodCallsLogger;
+
+public class Base_LifecycleAdapter implements GeneratedAdapter {
+
+    public Base_LifecycleAdapter(Base base) {
+    }
+
+    @Override
+    public void callMethods(LifecycleOwner source, Lifecycle.Event event, boolean onAny,
+            MethodCallsLogger logger) {
+
+    }
+}
diff --git a/android/arch/lifecycle/observers/DerivedSequence1.java b/android/arch/lifecycle/observers/DerivedSequence1.java
new file mode 100644
index 0000000..9db37f1
--- /dev/null
+++ b/android/arch/lifecycle/observers/DerivedSequence1.java
@@ -0,0 +1,23 @@
+/*
+ * 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.observers;
+
+public class DerivedSequence1 extends Base {
+
+    public void something() {
+    }
+}
diff --git a/android/arch/lifecycle/observers/DerivedSequence2.java b/android/arch/lifecycle/observers/DerivedSequence2.java
new file mode 100644
index 0000000..f2ef943
--- /dev/null
+++ b/android/arch/lifecycle/observers/DerivedSequence2.java
@@ -0,0 +1,28 @@
+/*
+ * 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.observers;
+
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+public class DerivedSequence2 extends DerivedSequence1 {
+
+    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
+    void onStop() {
+
+    }
+}
diff --git a/android/arch/lifecycle/observers/DerivedWithNewMethods.java b/android/arch/lifecycle/observers/DerivedWithNewMethods.java
new file mode 100644
index 0000000..b1eaef0
--- /dev/null
+++ b/android/arch/lifecycle/observers/DerivedWithNewMethods.java
@@ -0,0 +1,28 @@
+/*
+ * 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.observers;
+
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+public class DerivedWithNewMethods extends Base {
+
+    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
+    void onStop() {
+
+    }
+}
diff --git a/android/arch/lifecycle/observers/DerivedWithNoNewMethods.java b/android/arch/lifecycle/observers/DerivedWithNoNewMethods.java
new file mode 100644
index 0000000..cb1afb8
--- /dev/null
+++ b/android/arch/lifecycle/observers/DerivedWithNoNewMethods.java
@@ -0,0 +1,20 @@
+/*
+ * 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.observers;
+
+public class DerivedWithNoNewMethods extends Base {
+}
diff --git a/android/arch/lifecycle/observers/DerivedWithOverridenMethodsWithLfAnnotation.java b/android/arch/lifecycle/observers/DerivedWithOverridenMethodsWithLfAnnotation.java
new file mode 100644
index 0000000..40c7c9a
--- /dev/null
+++ b/android/arch/lifecycle/observers/DerivedWithOverridenMethodsWithLfAnnotation.java
@@ -0,0 +1,29 @@
+/*
+ * 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.observers;
+
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+public class DerivedWithOverridenMethodsWithLfAnnotation extends Base {
+
+    @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
+    @Override
+    public void onCreate() {
+        super.onCreate();
+    }
+}
diff --git a/android/arch/lifecycle/observers/Interface1.java b/android/arch/lifecycle/observers/Interface1.java
new file mode 100644
index 0000000..e193de9
--- /dev/null
+++ b/android/arch/lifecycle/observers/Interface1.java
@@ -0,0 +1,27 @@
+/*
+ * 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.observers;
+
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LifecycleObserver;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+public interface Interface1 extends LifecycleObserver {
+
+    @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
+    void onCreate();
+}
diff --git a/android/arch/lifecycle/observers/Interface1_LifecycleAdapter.java b/android/arch/lifecycle/observers/Interface1_LifecycleAdapter.java
new file mode 100644
index 0000000..c597b1c
--- /dev/null
+++ b/android/arch/lifecycle/observers/Interface1_LifecycleAdapter.java
@@ -0,0 +1,34 @@
+/*
+ * 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.observers;
+
+import android.arch.lifecycle.GeneratedAdapter;
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LifecycleOwner;
+import android.arch.lifecycle.MethodCallsLogger;
+
+public class Interface1_LifecycleAdapter implements GeneratedAdapter {
+
+    public Interface1_LifecycleAdapter(Interface1 base) {
+    }
+
+    @Override
+    public void callMethods(LifecycleOwner source, Lifecycle.Event event, boolean onAny,
+            MethodCallsLogger logger) {
+
+    }
+}
diff --git a/android/arch/lifecycle/observers/Interface2.java b/android/arch/lifecycle/observers/Interface2.java
new file mode 100644
index 0000000..1056fcb
--- /dev/null
+++ b/android/arch/lifecycle/observers/Interface2.java
@@ -0,0 +1,30 @@
+/*
+ * 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.observers;
+
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LifecycleObserver;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+public interface Interface2 extends LifecycleObserver {
+
+    @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
+    void onCreate();
+
+    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
+    void onDestroy();
+}
diff --git a/android/arch/lifecycle/observers/Interface2_LifecycleAdapter.java b/android/arch/lifecycle/observers/Interface2_LifecycleAdapter.java
new file mode 100644
index 0000000..b05b41a
--- /dev/null
+++ b/android/arch/lifecycle/observers/Interface2_LifecycleAdapter.java
@@ -0,0 +1,34 @@
+/*
+ * 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.observers;
+
+import android.arch.lifecycle.GeneratedAdapter;
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LifecycleOwner;
+import android.arch.lifecycle.MethodCallsLogger;
+
+public class Interface2_LifecycleAdapter implements GeneratedAdapter {
+
+    public Interface2_LifecycleAdapter(Interface2 base) {
+    }
+
+    @Override
+    public void callMethods(LifecycleOwner source, Lifecycle.Event event, boolean onAny,
+            MethodCallsLogger logger) {
+
+    }
+}
diff --git a/android/arch/lifecycle/observers/InterfaceImpl1.java b/android/arch/lifecycle/observers/InterfaceImpl1.java
new file mode 100644
index 0000000..2f03393
--- /dev/null
+++ b/android/arch/lifecycle/observers/InterfaceImpl1.java
@@ -0,0 +1,23 @@
+/*
+ * 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.observers;
+
+public class InterfaceImpl1 implements Interface1 {
+    @Override
+    public void onCreate() {
+    }
+}
diff --git a/android/arch/lifecycle/observers/InterfaceImpl2.java b/android/arch/lifecycle/observers/InterfaceImpl2.java
new file mode 100644
index 0000000..eef8ce4
--- /dev/null
+++ b/android/arch/lifecycle/observers/InterfaceImpl2.java
@@ -0,0 +1,28 @@
+/*
+ * 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.observers;
+
+public class InterfaceImpl2 implements Interface1, Interface2 {
+    @Override
+    public void onCreate() {
+    }
+
+    @Override
+    public void onDestroy() {
+
+    }
+}
diff --git a/android/arch/lifecycle/observers/InterfaceImpl3.java b/android/arch/lifecycle/observers/InterfaceImpl3.java
new file mode 100644
index 0000000..8f31808
--- /dev/null
+++ b/android/arch/lifecycle/observers/InterfaceImpl3.java
@@ -0,0 +1,23 @@
+/*
+ * 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.observers;
+
+public class InterfaceImpl3 extends Base implements Interface1 {
+    @Override
+    public void onCreate() {
+    }
+}
diff --git a/android/arch/lifecycle/testapp/FullLifecycleTestActivity.java b/android/arch/lifecycle/testapp/FullLifecycleTestActivity.java
index 5972b16..5f33c28 100644
--- a/android/arch/lifecycle/testapp/FullLifecycleTestActivity.java
+++ b/android/arch/lifecycle/testapp/FullLifecycleTestActivity.java
@@ -19,8 +19,8 @@
 import static android.arch.lifecycle.testapp.TestEvent.ACTIVITY_CALLBACK;
 
 import android.arch.lifecycle.Lifecycle;
-import android.arch.lifecycle.LifecycleActivity;
 import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
 import android.util.Pair;
 
 import java.util.ArrayList;
@@ -31,7 +31,7 @@
 /**
  * Activity for testing full lifecycle
  */
-public class FullLifecycleTestActivity extends LifecycleActivity implements CollectingActivity {
+public class FullLifecycleTestActivity extends FragmentActivity implements CollectingActivity {
 
     private List<Pair<TestEvent, Lifecycle.Event>> mCollectedEvents = new ArrayList<>();
     private TestObserver mTestObserver = new TestObserver(mCollectedEvents);
diff --git a/android/arch/lifecycle/testapp/LifecycleTestActivity.java b/android/arch/lifecycle/testapp/LifecycleTestActivity.java
index 093ec7f..cf07aee 100644
--- a/android/arch/lifecycle/testapp/LifecycleTestActivity.java
+++ b/android/arch/lifecycle/testapp/LifecycleTestActivity.java
@@ -16,13 +16,13 @@
 
 package android.arch.lifecycle.testapp;
 
-import android.arch.lifecycle.LifecycleActivity;
 import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
 
 /**
  * Activity for testing events by themselves
  */
-public class LifecycleTestActivity extends LifecycleActivity {
+public class LifecycleTestActivity extends FragmentActivity {
 
     /**
      * identifies that
diff --git a/android/arch/lifecycle/testapp/NavigationDialogActivity.java b/android/arch/lifecycle/testapp/NavigationDialogActivity.java
index 709bd8d..0ae9403 100644
--- a/android/arch/lifecycle/testapp/NavigationDialogActivity.java
+++ b/android/arch/lifecycle/testapp/NavigationDialogActivity.java
@@ -16,10 +16,10 @@
 
 package android.arch.lifecycle.testapp;
 
-import android.arch.lifecycle.LifecycleActivity;
+import android.support.v4.app.FragmentActivity;
 
 /**
  *  an activity with Dialog theme.
  */
-public class NavigationDialogActivity extends LifecycleActivity {
+public class NavigationDialogActivity extends FragmentActivity {
 }
diff --git a/android/arch/lifecycle/testapp/NavigationTestActivityFirst.java b/android/arch/lifecycle/testapp/NavigationTestActivityFirst.java
index f1847c9..69fd478 100644
--- a/android/arch/lifecycle/testapp/NavigationTestActivityFirst.java
+++ b/android/arch/lifecycle/testapp/NavigationTestActivityFirst.java
@@ -16,10 +16,10 @@
 
 package android.arch.lifecycle.testapp;
 
-import android.arch.lifecycle.LifecycleActivity;
+import android.support.v4.app.FragmentActivity;
 
 /**
  * Activity for ProcessOwnerTest
  */
-public class NavigationTestActivityFirst extends LifecycleActivity {
+public class NavigationTestActivityFirst extends FragmentActivity {
 }
diff --git a/android/arch/lifecycle/testapp/NavigationTestActivitySecond.java b/android/arch/lifecycle/testapp/NavigationTestActivitySecond.java
index 221e927..0f9a4c9 100644
--- a/android/arch/lifecycle/testapp/NavigationTestActivitySecond.java
+++ b/android/arch/lifecycle/testapp/NavigationTestActivitySecond.java
@@ -16,10 +16,10 @@
 
 package android.arch.lifecycle.testapp;
 
-import android.arch.lifecycle.LifecycleActivity;
+import android.support.v4.app.FragmentActivity;
 
 /**
  * Activity for ProcessOwnerTest
  */
-public class NavigationTestActivitySecond extends LifecycleActivity {
+public class NavigationTestActivitySecond extends FragmentActivity {
 }
diff --git a/android/arch/lifecycle/testapp/SimpleAppLifecycleTestActivity.java b/android/arch/lifecycle/testapp/SimpleAppLifecycleTestActivity.java
index 6d61c5e..77bd99f 100644
--- a/android/arch/lifecycle/testapp/SimpleAppLifecycleTestActivity.java
+++ b/android/arch/lifecycle/testapp/SimpleAppLifecycleTestActivity.java
@@ -17,12 +17,12 @@
 package android.arch.lifecycle.testapp;
 
 import android.arch.lifecycle.Lifecycle;
-import android.arch.lifecycle.LifecycleActivity;
 import android.arch.lifecycle.LifecycleObserver;
 import android.arch.lifecycle.LifecycleOwner;
 import android.arch.lifecycle.OnLifecycleEvent;
 import android.arch.lifecycle.ProcessLifecycleOwner;
 import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
 import android.util.Pair;
 
 import java.util.ArrayList;
@@ -33,7 +33,7 @@
 /**
  * Activity for SimpleAppFullLifecycleTest
  */
-public class SimpleAppLifecycleTestActivity extends LifecycleActivity {
+public class SimpleAppLifecycleTestActivity extends FragmentActivity {
 
     public enum TestEventType {
         PROCESS_EVENT,
diff --git a/android/arch/lifecycle/viewmodeltest/ViewModelActivity.java b/android/arch/lifecycle/viewmodeltest/ViewModelActivity.java
index 5ef9f16..1f9f100 100644
--- a/android/arch/lifecycle/viewmodeltest/ViewModelActivity.java
+++ b/android/arch/lifecycle/viewmodeltest/ViewModelActivity.java
@@ -16,15 +16,14 @@
 
 package android.arch.lifecycle.viewmodeltest;
 
+import android.arch.lifecycle.ViewModelProviders;
 import android.arch.lifecycle.extensions.test.R;
 import android.os.Bundle;
 import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
 
-import android.arch.lifecycle.LifecycleActivity;
-import android.arch.lifecycle.LifecycleFragment;
-import android.arch.lifecycle.ViewModelProviders;
-
-public class ViewModelActivity extends LifecycleActivity {
+public class ViewModelActivity extends FragmentActivity {
     public static final String KEY_FRAGMENT_MODEL = "fragment-model";
     public static final String KEY_ACTIVITY_MODEL = "activity-model";
     public static final String FRAGMENT_TAG_1 = "f1";
@@ -47,7 +46,7 @@
         defaultActivityModel = ViewModelProviders.of(this).get(TestViewModel.class);
     }
 
-    public static class ViewModelFragment extends LifecycleFragment {
+    public static class ViewModelFragment extends Fragment {
         public TestViewModel fragmentModel;
         public TestViewModel activityModel;
         public TestViewModel defaultActivityModel;
diff --git a/android/arch/paging/BoundedDataSource.java b/android/arch/paging/BoundedDataSource.java
index 96c23fc..664ab16 100644
--- a/android/arch/paging/BoundedDataSource.java
+++ b/android/arch/paging/BoundedDataSource.java
@@ -18,6 +18,7 @@
 
 import android.support.annotation.Nullable;
 import android.support.annotation.RestrictTo;
+import android.support.annotation.WorkerThread;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -49,15 +50,18 @@
      * @return List of loaded items. Null if the BoundedDataSource is no longer valid, and should
      *         not be queried again.
      */
+    @WorkerThread
     @Nullable
     public abstract List<Value> loadRange(int startPosition, int loadCount);
 
+    @WorkerThread
     @Nullable
     @Override
     public List<Value> loadAfter(int startIndex, int pageSize) {
         return loadRange(startIndex, pageSize);
     }
 
+    @WorkerThread
     @Nullable
     @Override
     public List<Value> loadBefore(int startIndex, int pageSize) {
diff --git a/android/arch/paging/ContiguousDataSource.java b/android/arch/paging/ContiguousDataSource.java
index 9ff1117..afcc208 100644
--- a/android/arch/paging/ContiguousDataSource.java
+++ b/android/arch/paging/ContiguousDataSource.java
@@ -41,6 +41,8 @@
         return true;
     }
 
+    /** @hide */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     @WorkerThread
     @Nullable
     public abstract NullPaddedList<Value> loadInitial(
@@ -58,7 +60,10 @@
      * @param pageSize        Suggested number of items to load.
      * @return List of items, starting at position currentEndIndex + 1. Null if the data source is
      * no longer valid, and should not be queried again.
+     *
+     * @hide
      */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     @WorkerThread
     @Nullable
     public final List<Value> loadAfter(int currentEndIndex,
@@ -88,7 +93,10 @@
      *                          on item contents.
      * @param pageSize          Suggested number of items to load.
      * @return List of items, in descending order, starting at position currentBeginIndex - 1.
+     *
+     * @hide
      */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     @WorkerThread
     @Nullable
     public final List<Value> loadBefore(int currentBeginIndex,
diff --git a/android/arch/paging/DataSource.java b/android/arch/paging/DataSource.java
index 1e81569..48fbec5 100644
--- a/android/arch/paging/DataSource.java
+++ b/android/arch/paging/DataSource.java
@@ -17,6 +17,7 @@
 package android.arch.paging;
 
 import android.support.annotation.AnyThread;
+import android.support.annotation.WorkerThread;
 
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -64,6 +65,7 @@
      * @return number of items that this DataSource can provide in total, or
      * {@link #COUNT_UNDEFINED} if expensive or undesired to compute.
      */
+    @WorkerThread
     public abstract int countItems();
 
     /**
@@ -143,6 +145,7 @@
      *
      * @return True if the data source is invalid, and can no longer return data.
      */
+    @WorkerThread
     public boolean isInvalid() {
         return mInvalid.get();
     }
diff --git a/android/arch/paging/KeyedDataSource.java b/android/arch/paging/KeyedDataSource.java
index 057eb7f..8cf6829 100644
--- a/android/arch/paging/KeyedDataSource.java
+++ b/android/arch/paging/KeyedDataSource.java
@@ -16,6 +16,7 @@
 
 package android.arch.paging;
 
+import android.support.annotation.AnyThread;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.annotation.RestrictTo;
@@ -54,7 +55,7 @@
  *     {@literal @}SuppressWarnings("FieldCanBeLocal")
  *     private final InvalidationTracker.Observer mObserver;
  *
- *     public OffsetUserQueryDataSource(MyDatabase db) {
+ *     public KeyedUserQueryDataSource(MyDatabase db) {
  *         mDb = db;
  *         mUserDao = db.getUserDao();
  *         mObserver = new InvalidationTracker.Observer("user") {
@@ -85,11 +86,15 @@
  *
  *     {@literal @}Override
  *     public List&lt;User> loadBefore({@literal @}NonNull String userName, int pageSize) {
+ *         // Return items adjacent to 'userName' in reverse order
+ *         // it's valid to return a different-sized list of items than pageSize, if it's easier
  *         return mUserDao.userNameLoadBefore(userName, pageSize);
  *     }
  *
  *     {@literal @}Override
  *     public List&lt;User> loadAfter({@literal @}Nullable String userName, int pageSize) {
+ *         // Return items adjacent to 'userName'
+ *         // it's valid to return a different-sized list of items than pageSize, if it's easier
  *         return mUserDao.userNameLoadAfter(userName, pageSize);
  *     }
  * }</pre>
@@ -198,13 +203,64 @@
         return list;
     }
 
+    /**
+     * Return a key associated with the given item.
+     * <p>
+     * If your KeyedDataSource is loading from a source that is sorted and loaded by a unique
+     * integer ID, you would return {@code item.getID()} here. This key can then be passed to
+     * {@link #loadBefore(Key, int)} or {@link #loadAfter(Key, int)} to load additional items
+     * adjacent to the item passed to this function.
+     * <p>
+     * If your key is more complex, such as when you're sorting by name, then resolving collisions
+     * with integer ID, you'll need to return both. In such a case you would use a wrapper class,
+     * such as {@code Pair<String, Integer>} or, in Kotlin,
+     * {@code data class Key(val name: String, val id: Int)}
+     *
+     * @param item Item to get the key from.
+     * @return Key associated with given item.
+     */
     @NonNull
+    @AnyThread
     public abstract Key getKey(@NonNull Value item);
 
+    /**
+     * Return the number of items that occur before the item uniquely identified by {@code key} in
+     * the data set.
+     * <p>
+     * For example, if you're loading items sorted by ID, then this would return the total number of
+     * items with ID less than {@code key}.
+     * <p>
+     * If you return {@link #COUNT_UNDEFINED} here, or from {@link #countItemsAfter(Key)}, your
+     * data source will not present placeholder null items in place of unloaded data.
+     *
+     * @param key A unique identifier of an item in the data set.
+     * @return Number of items in the data set before the item identified by {@code key}, or
+     *         {@link #COUNT_UNDEFINED}.
+     *
+     * @see #countItemsAfter(Key)
+     */
+    @WorkerThread
     public int countItemsBefore(@NonNull Key key) {
         return COUNT_UNDEFINED;
     }
 
+    /**
+     * Return the number of items that occur after the item uniquely identified by {@code key} in
+     * the data set.
+     * <p>
+     * For example, if you're loading items sorted by ID, then this would return the total number of
+     * items with ID greater than {@code key}.
+     * <p>
+     * If you return {@link #COUNT_UNDEFINED} here, or from {@link #countItemsBefore(Key)}, your
+     * data source will not present placeholder null items in place of unloaded data.
+     *
+     * @param key A unique identifier of an item in the data set.
+     * @return Number of items in the data set after the item identified by {@code key}, or
+     *         {@link #COUNT_UNDEFINED}.
+     *
+     * @see #countItemsBefore(Key)
+     */
+    @WorkerThread
     public int countItemsAfter(@NonNull Key key) {
         return COUNT_UNDEFINED;
     }
@@ -231,10 +287,17 @@
     public abstract List<Value> loadAfter(@NonNull Key currentEndKey, int pageSize);
 
     /**
-     * Load data before the currently loaded content, starting at the provided index.
+     * Load data before the currently loaded content, starting at the provided index,
+     * in reverse-display order.
      * <p>
      * It's valid to return a different list size than the page size, if it's easier for this data
      * source. It is generally safer to increase the number loaded than reduce.
+     * <p class="note"><strong>Note:</strong> Items returned from loadBefore <em>must</em> be in
+     * reverse order from how they will be presented in the list. The first item in the return list
+     * will be prepended immediately before the current beginning of the list. This is so that the
+     * KeyedDataSource may return a different number of items from the requested {@code pageSize} by
+     * shortening or lengthening the return list as it desires.
+     * <p>
      *
      * @param currentBeginKey Load items before this key.
      * @param pageSize         Suggested number of items to load.
diff --git a/android/arch/paging/PagedList.java b/android/arch/paging/PagedList.java
index 0d5313d..6a31b68 100644
--- a/android/arch/paging/PagedList.java
+++ b/android/arch/paging/PagedList.java
@@ -27,16 +27,65 @@
 /**
  * Lazy loading list that pages in content from a {@link DataSource}.
  * <p>
- * A PagedList is a lazy loaded list, which presents data from a {@link DataSource}. If the
- * DataSource is counted (returns a valid number from its count method(s)), the PagedList will
- * present {@code null} items in place of not-yet-loaded content to serve as placeholders.
+ * A PagedList is a {@link List} which loads its data in chunks (pages) from a {@link DataSource}.
+ * Items can be accessed with {@link #get(int)}, and further loading can be triggered with
+ * {@link #loadAround(int)}. See {@link PagedListAdapter}, which enables the binding of a PagedList
+ * to a {@link android.support.v7.widget.RecyclerView}.
+ * <h4>Loading Data</h4>
  * <p>
- * When {@link #loadAround} is called, items will be loaded in near the passed position. If
- * placeholder {@code null}s are present in the list, they will be replaced as content is loaded.
+ * All data in a PagedList is loaded from its {@link DataSource}. Creating a PagedList loads data
+ * from the DataSource immediately, and should for this reason be done on a background thread. The
+ * constructed PagedList may then be passed to and used on the UI thread. This is done to prevent
+ * passing a list with no loaded content to the UI thread, which should generally not be presented
+ * to the user.
  * <p>
- * In this way, PagedList can present data for an unbounded, infinite scrolling list, or a very
- * large but countable list. See {@link PagedListAdapter}, which enables the binding of a PagedList
- * to a RecyclerView. Use {@link Config} to control how many items a PagedList loads, and when.
+ * When {@link #loadAround} is called, items will be loaded in near the passed list index. If
+ * placeholder {@code null}s are present in the list, they will be replaced as content is
+ * loaded. If not, newly loaded items will be inserted at the beginning or end of the list.
+ * <p>
+ * PagedList can present data for an unbounded, infinite scrolling list, or a very large but
+ * countable list. Use {@link Config} to control how many items a PagedList loads, and when.
+ * <p>
+ * If you use {@link LivePagedListProvider} to get a
+ * {@link android.arch.lifecycle.LiveData}&lt;PagedList>, it will initialize PagedLists on a
+ * background thread for you.
+ * <h4>Placeholders</h4>
+ * <p>
+ * There are two ways that PagedList can represent its not-yet-loaded data - with or without
+ * {@code null} placeholders.
+ * <p>
+ * With placeholders, the PagedList is always the full size of the data set. {@code get(N)} returns
+ * the {@code N}th item in the data set, or {@code null} if its not yet loaded.
+ * <p>
+ * Without {@code null} placeholders, the PagedList is the sublist of data that has already been
+ * loaded. The size of the PagedList is the number of currently loaded items, and {@code get(N)}
+ * returns the {@code N}th <em>loaded</em> item. This is not necessarily the {@code N}th item in the
+ * data set.
+ * <p>
+ * Placeholders have several benefits:
+ * <ul>
+ *     <li>They express the full sized list to the presentation layer (often a
+ *     {@link PagedListAdapter}), and so can support scrollbars (without jumping as pages are
+ *     loaded) and fast-scrolling to any position, whether loaded or not.
+ *     <li>They avoid the need for a loading spinner at the end of the loaded list, since the list
+ *     is always full sized.
+ * </ul>
+ * <p>
+ * They also have drawbacks:
+ * <ul>
+ *     <li>Your Adapter (or other presentation mechanism) needs to account for {@code null} items.
+ *     This often means providing default values in data you bind to a
+ *     {@link android.support.v7.widget.RecyclerView.ViewHolder}.
+ *     <li>They don't work well if your item views are of different sizes, as this will prevent
+ *     loading items from cross-fading nicely.
+ *     <li>They require you to count your data set, which can be expensive or impossible, depending
+ *     on where your data comes from.
+ * </ul>
+ * <p>
+ * Placeholders are enabled by default, but can be disabled in two ways. They are disabled if the
+ * DataSource returns {@link DataSource#COUNT_UNDEFINED} from any item counting method, or if
+ * {@code false} is passed to {@link Config.Builder#setEnablePlaceholders(boolean)} when building a
+ * {@link Config}.
  *
  * @param <T> The type of the entries in the list.
  */
@@ -92,6 +141,18 @@
      * Builder class for PagedList.
      * <p>
      * DataSource, main thread and background executor, and Config must all be provided.
+     * <p>
+     * A valid PagedList may not be constructed without data, so building a PagedList queries
+     * initial data from the data source. This is done because it's generally undesired to present a
+     * PagedList with no data in it to the UI. It's better to present initial data, so that the UI
+     * doesn't show an empty list, or placeholders for a few frames, just before showing initial
+     * content.
+     * <p>
+     * Because PagedLists are initialized with data, PagedLists must be built on a background
+     * thread.
+     * <p>
+     * {@link LivePagedListProvider} does this creation on a background thread automatically, if you
+     * want to receive a {@code LiveData<PagedList<...>>}.
      *
      * @param <Key> Type of key used to load data from the DataSource.
      * @param <Value> Type of items held and loaded by the PagedList.
@@ -174,11 +235,17 @@
          * <p>
          * This call will initial data and perform any counting needed to initialize the PagedList,
          * therefore it should only be called on a worker thread.
+         * <p>
+         * While build() will always return a PagedList, it's important to note that the PagedList
+         * initial load may fail to acquire data from the DataSource. This can happen for example if
+         * the DataSource is invalidated during its initial load. If this happens, the PagedList
+         * will be immediately {@link PagedList#isDetached() detached}, and you can retry
+         * construction (including setting a new DataSource).
          *
          * @return The newly constructed PagedList
          */
-        @NonNull
         @WorkerThread
+        @NonNull
         public PagedList<Value> build() {
             if (mDataSource == null) {
                 throw new IllegalArgumentException("DataSource required");
@@ -403,6 +470,16 @@
              * Defines the number of items loaded at once from the DataSource.
              * <p>
              * Should be several times the number of visible items onscreen.
+             * <p>
+             * Configuring your page size depends on how your data is being loaded and used. Smaller
+             * page sizes improve memory usage, latency, and avoid GC churn. Larger pages generally
+             * improve loading throughput, to a point
+             * (avoid loading more than 2MB from SQLite at once, since it incurs extra cost).
+             * <p>
+             * If you're loading data for very large, social-media style cards that take up most of
+             * a screen, and your database isn't a bottleneck, 10-20 may make sense. If you're
+             * displaying dozens of items in a tiled grid, which can present items during a scroll
+             * much more quickly, consider closer to 100.
              *
              * @param pageSize Number of items loaded at once from the DataSource.
              * @return this
@@ -414,12 +491,15 @@
 
             /**
              * Defines how far from the edge of loaded content an access must be to trigger further
-             * loading. Defaults to page size.
-             * <p>
-             * A value of 0 indicates that no list items will be loaded before they are first
-             * requested.
+             * loading.
              * <p>
              * Should be several times the number of visible items onscreen.
+             * <p>
+             * If not set, defaults to page size.
+             * <p>
+             * A value of 0 indicates that no list items will be loaded until they are specifically
+             * requested. This is generally not recommended, so that users don't observe a
+             * placeholder item (with placeholders) or end of list (without) while scrolling.
              *
              * @param prefetchDistance Distance the PagedList should prefetch.
              * @return this
@@ -432,8 +512,10 @@
             /**
              * Pass false to disable null placeholders in PagedLists using this Config.
              * <p>
-             * A PagedList will present null placeholders for not yet loaded content if two
-             * contitions are met:
+             * If not set, defaults to true.
+             * <p>
+             * A PagedList will present null placeholders for not-yet-loaded content if two
+             * conditions are met:
              * <p>
              * 1) Its DataSource can count all unloaded items (so that the number of nulls to
              * present is known).
@@ -442,6 +524,13 @@
              * <p>
              * Call {@code setEnablePlaceholders(false)} to ensure the receiver of the PagedList
              * (often a {@link PagedListAdapter}) doesn't need to account for null items.
+             * <p>
+             * If placeholders are disabled, not-yet-loaded content will not be present in the list.
+             * Paging will still occur, but as items are loaded or removed, they will be signaled
+             * as inserts to the {@link PagedList.Callback}.
+             * {@link PagedList.Callback#onChanged(int, int)} will not be issued as part of loading,
+             * though a {@link PagedListAdapter} may still receive change events as a result of
+             * PagedList diffing.
              *
              * @param enablePlaceholders False if null placeholders should be disabled.
              * @return this
diff --git a/android/arch/paging/PagedListAdapter.java b/android/arch/paging/PagedListAdapter.java
index 19a0c55..93c02ea 100644
--- a/android/arch/paging/PagedListAdapter.java
+++ b/android/arch/paging/PagedListAdapter.java
@@ -51,6 +51,7 @@
  *     public final LiveData&lt;PagedList&lt;User>> usersList;
  *     public MyViewModel(UserDao userDao) {
  *         usersList = userDao.usersByLastName().create(
+ *                 /* initial load position {@literal *}/ 0,
  *                 new PagedList.Config.Builder()
  *                         .setPageSize(50)
  *                         .setPrefetchDistance(50)
@@ -72,7 +73,7 @@
  *
  * class UserAdapter extends PagedListAdapter&lt;User, UserViewHolder> {
  *     public UserAdapter() {
- *         super(User.DIFF_CALLBACK);
+ *         super(DIFF_CALLBACK);
  *     }
  *     {@literal @}Override
  *     public void onBindViewHolder(UserViewHolder holder, int position) {
@@ -85,27 +86,21 @@
  *             holder.clear();
  *         }
  *     }
- * }
- *
- * {@literal @}Entity
- * class User {
- *      // ... simple POJO code omitted ...
- *
- *      public static final DiffCallback&lt;User> DIFF_CALLBACK = new DiffCallback&lt;Customer>() {
- *          {@literal @}Override
- *          public boolean areItemsTheSame(
- *                  {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) {
- *              // User properties may have changed if reloaded from the DB, but ID is fixed
- *              return oldUser.getId() == newUser.getId();
- *          }
- *          {@literal @}Override
- *          public boolean areContentsTheSame(
- *                  {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) {
- *              // NOTE: if you use equals, your object must properly override Object#equals()
- *              // Incorrectly returning false here will result in too many animations.
- *              return oldUser.equals(newUser);
- *          }
- *      }
+ *     public static final DiffCallback&lt;User> DIFF_CALLBACK = new DiffCallback&lt;User>() {
+ *         {@literal @}Override
+ *         public boolean areItemsTheSame(
+ *                 {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) {
+ *             // User properties may have changed if reloaded from the DB, but ID is fixed
+ *             return oldUser.getId() == newUser.getId();
+ *         }
+ *         {@literal @}Override
+ *         public boolean areContentsTheSame(
+ *                 {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) {
+ *             // NOTE: if you use equals, your object must properly override Object#equals()
+ *             // Incorrectly returning false here will result in too many animations.
+ *             return oldUser.equals(newUser);
+ *         }
+ *     }
  * }</pre>
  *
  * Advanced users that wish for more control over adapter behavior, or to provide a specific base
diff --git a/android/arch/paging/PagedListAdapterHelper.java b/android/arch/paging/PagedListAdapterHelper.java
index 4bff5fc..c7b61d9 100644
--- a/android/arch/paging/PagedListAdapterHelper.java
+++ b/android/arch/paging/PagedListAdapterHelper.java
@@ -57,6 +57,7 @@
  *     public final LiveData&lt;PagedList&lt;User>> usersList;
  *     public MyViewModel(UserDao userDao) {
  *         usersList = userDao.usersByLastName().create(
+ *                 /* initial load position {@literal *}/ 0,
  *                 new PagedList.Config.Builder()
  *                         .setPageSize(50)
  *                         .setPrefetchDistance(50)
@@ -79,7 +80,7 @@
  * class UserAdapter extends RecyclerView.Adapter&lt;UserViewHolder> {
  *     private final PagedListAdapterHelper&lt;User> mHelper;
  *     public UserAdapter(PagedListAdapterHelper.Builder&lt;User> builder) {
- *         mHelper = new PagedListAdapterHelper(this, User.DIFF_CALLBACK);
+ *         mHelper = new PagedListAdapterHelper(this, DIFF_CALLBACK);
  *     }
  *     {@literal @}Override
  *     public int getItemCount() {
@@ -99,13 +100,7 @@
  *             holder.clear();
  *         }
  *     }
- * }
- *
- * {@literal @}Entity
- * class User {
- *      // ... simple POJO code omitted ...
- *
- *      public static final DiffCallback&lt;User> DIFF_CALLBACK = new DiffCallback&lt;Customer>() {
+ *     public static final DiffCallback&lt;User> DIFF_CALLBACK = new DiffCallback&lt;User>() {
  *          {@literal @}Override
  *          public boolean areItemsTheSame(
  *                  {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) {
diff --git a/android/arch/paging/TestExecutor.java b/android/arch/paging/TestExecutor.java
index 976f7df..30809c3 100644
--- a/android/arch/paging/TestExecutor.java
+++ b/android/arch/paging/TestExecutor.java
@@ -30,7 +30,7 @@
         mTasks.add(command);
     }
 
-    public boolean executeAll() {
+    boolean executeAll() {
         boolean consumed = !mTasks.isEmpty();
         Runnable task;
         while ((task = mTasks.poll()) != null) {
diff --git a/android/arch/paging/TiledDataSource.java b/android/arch/paging/TiledDataSource.java
index 56556cd..36be423 100644
--- a/android/arch/paging/TiledDataSource.java
+++ b/android/arch/paging/TiledDataSource.java
@@ -17,6 +17,7 @@
 package android.arch.paging;
 
 import android.support.annotation.Nullable;
+import android.support.annotation.WorkerThread;
 
 import java.util.List;
 
@@ -90,6 +91,7 @@
      *
      * @return Number of items this DataSource can provide. Must be <code>0</code> or greater.
      */
+    @WorkerThread
     @Override
     public abstract int countItems();
 
@@ -100,14 +102,20 @@
 
     /**
      * Called to load items at from the specified position range.
+     * <p>
+     * This method must return a list of requested size, unless at the end of list. Fixed size pages
+     * enable TiledDataSource to navigate tiles efficiently, and quickly accesss any position in the
+     * data set.
+     * <p>
+     * If a list of a different size is returned, but it is not the last list in the data set based
+     * on the return value from {@link #countItems()}, an exception will be thrown.
      *
      * @param startPosition Index of first item to load.
-     * @param count         Exact number of items to load. Returning a different number will cause
-     *                      an exception to be thrown.
-     * @return List of loaded items. Null if the DataSource is no longer valid, and should
-     *         not be queried again.
+     * @param count         Number of items to load.
+     * @return List of loaded items, of the requested length unless at end of list. Null if the
+     *         DataSource is no longer valid, and should not be queried again.
      */
-    @SuppressWarnings("WeakerAccess")
+    @WorkerThread
     public abstract List<Type> loadRange(int startPosition, int count);
 
     final List<Type> loadRangeWrapper(int startPosition, int count) {
@@ -132,6 +140,7 @@
             mTiledDataSource = tiledDataSource;
         }
 
+        @WorkerThread
         @Nullable
         @Override
         public List<Value> loadRange(int startPosition, int loadCount) {
diff --git a/android/arch/persistence/db/SupportSQLiteOpenHelper.java b/android/arch/persistence/db/SupportSQLiteOpenHelper.java
index 5a96e5a..02e4e7d 100644
--- a/android/arch/persistence/db/SupportSQLiteOpenHelper.java
+++ b/android/arch/persistence/db/SupportSQLiteOpenHelper.java
@@ -17,13 +17,18 @@
 package android.arch.persistence.db;
 
 import android.content.Context;
-import android.database.DatabaseErrorHandler;
-import android.database.DefaultDatabaseErrorHandler;
+import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteException;
 import android.os.Build;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.annotation.RequiresApi;
+import android.util.Log;
+import android.util.Pair;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
 
 /**
  * An interface to map the behavior of {@link android.database.sqlite.SQLiteOpenHelper}.
@@ -99,10 +104,29 @@
     void close();
 
     /**
-     * Matching callback methods from {@link android.database.sqlite.SQLiteOpenHelper}.
+     * Handles various lifecycle events for the SQLite connection, similar to
+     * {@link android.database.sqlite.SQLiteOpenHelper}.
      */
     @SuppressWarnings({"unused", "WeakerAccess"})
     abstract class Callback {
+        private static final String TAG = "SupportSQLite";
+        /**
+         * Version number of the database (starting at 1); if the database is older,
+         * {@link SupportSQLiteOpenHelper.Callback#onUpgrade(SupportSQLiteDatabase, int, int)}
+         * will be used to upgrade the database; if the database is newer,
+         * {@link SupportSQLiteOpenHelper.Callback#onDowngrade(SupportSQLiteDatabase, int, int)}
+         * will be used to downgrade the database.
+         */
+        public final int version;
+
+        /**
+         * Creates a new Callback to get database lifecycle events.
+         * @param version The version for the database instance. See {@link #version}.
+         */
+        public Callback(int version) {
+            this.version = version;
+        }
+
         /**
          * Called when the database connection is being configured, to enable features such as
          * write-ahead logging or foreign key support.
@@ -193,6 +217,81 @@
         public void onOpen(SupportSQLiteDatabase db) {
 
         }
+
+        /**
+         * The method invoked when database corruption is detected. Default implementation will
+         * delete the database file.
+         *
+         * @param db the {@link SupportSQLiteDatabase} object representing the database on which
+         *           corruption is detected.
+         */
+        public void onCorruption(SupportSQLiteDatabase db) {
+            // the following implementation is taken from {@link DefaultDatabaseErrorHandler}.
+
+            Log.e(TAG, "Corruption reported by sqlite on database: " + db.getPath());
+            // is the corruption detected even before database could be 'opened'?
+            if (!db.isOpen()) {
+                // database files are not even openable. delete this database file.
+                // NOTE if the database has attached databases, then any of them could be corrupt.
+                // and not deleting all of them could cause corrupted database file to remain and
+                // make the application crash on database open operation. To avoid this problem,
+                // the application should provide its own {@link DatabaseErrorHandler} impl class
+                // to delete ALL files of the database (including the attached databases).
+                deleteDatabaseFile(db.getPath());
+                return;
+            }
+
+            List<Pair<String, String>> attachedDbs = null;
+            try {
+                // Close the database, which will cause subsequent operations to fail.
+                // before that, get the attached database list first.
+                try {
+                    attachedDbs = db.getAttachedDbs();
+                } catch (SQLiteException e) {
+                /* ignore */
+                }
+                try {
+                    db.close();
+                } catch (IOException e) {
+                /* ignore */
+                }
+            } finally {
+                // Delete all files of this corrupt database and/or attached databases
+                if (attachedDbs != null) {
+                    for (Pair<String, String> p : attachedDbs) {
+                        deleteDatabaseFile(p.second);
+                    }
+                } else {
+                    // attachedDbs = null is possible when the database is so corrupt that even
+                    // "PRAGMA database_list;" also fails. delete the main database file
+                    deleteDatabaseFile(db.getPath());
+                }
+            }
+        }
+
+        private void deleteDatabaseFile(String fileName) {
+            if (fileName.equalsIgnoreCase(":memory:") || fileName.trim().length() == 0) {
+                return;
+            }
+            Log.w(TAG, "deleting the database file: " + fileName);
+            try {
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+                    SQLiteDatabase.deleteDatabase(new File(fileName));
+                } else {
+                    try {
+                        final boolean deleted = new File(fileName).delete();
+                        if (!deleted) {
+                            Log.e(TAG, "Could not delete the database file " + fileName);
+                        }
+                    } catch (Exception error) {
+                        Log.e(TAG, "error while deleting corrupted database file", error);
+                    }
+                }
+            } catch (Exception e) {
+            /* print warning and ignore exception */
+                Log.w(TAG, "delete failed: ", e);
+            }
+        }
     }
 
     /**
@@ -211,33 +310,15 @@
         @Nullable
         public final String name;
         /**
-         * Version number of the database (starting at 1); if the database is older,
-         * {@link SupportSQLiteOpenHelper.Callback#onUpgrade(SupportSQLiteDatabase, int, int)}
-         * will be used to upgrade the database; if the database is newer,
-         * {@link SupportSQLiteOpenHelper.Callback#onDowngrade(SupportSQLiteDatabase, int, int)}
-         * will be used to downgrade the database.
-         */
-        public final int version;
-        /**
          * The callback class to handle creation, upgrade and downgrade.
          */
         @NonNull
         public final SupportSQLiteOpenHelper.Callback callback;
-        /**
-         * The {@link DatabaseErrorHandler} to be used when sqlite reports database
-         * corruption, or null to use the default error handler.
-         */
-        @Nullable
-        public final DatabaseErrorHandler errorHandler;
 
-        Configuration(@NonNull Context context, @Nullable String name,
-                int version, @Nullable DatabaseErrorHandler errorHandler,
-                @NonNull Callback callback) {
+        Configuration(@NonNull Context context, @Nullable String name, @NonNull Callback callback) {
             this.context = context;
             this.name = name;
-            this.version = version;
             this.callback = callback;
-            this.errorHandler = errorHandler;
         }
 
         /**
@@ -255,9 +336,7 @@
         public static class Builder {
             Context mContext;
             String mName;
-            int mVersion = 1;
             SupportSQLiteOpenHelper.Callback mCallback;
-            DatabaseErrorHandler mErrorHandler;
 
             public Configuration build() {
                 if (mCallback == null) {
@@ -268,11 +347,7 @@
                     throw new IllegalArgumentException("Must set a non-null context to create"
                             + " the configuration.");
                 }
-                if (mErrorHandler == null) {
-                    mErrorHandler = new DefaultDatabaseErrorHandler();
-                }
-                return new Configuration(mContext, mName, mVersion, mErrorHandler,
-                        mCallback);
+                return new Configuration(mContext, mName, mCallback);
             }
 
             Builder(@NonNull Context context) {
@@ -280,17 +355,6 @@
             }
 
             /**
-             * @param errorHandler The {@link DatabaseErrorHandler} to be used when sqlite
-             *                     reports database corruption, or null to use the default error
-             *                     handler.
-             * @return This
-             */
-            public Builder errorHandler(@Nullable DatabaseErrorHandler errorHandler) {
-                mErrorHandler = errorHandler;
-                return this;
-            }
-
-            /**
              * @param name Name of the database file, or null for an in-memory database.
              * @return This
              */
@@ -307,20 +371,6 @@
                 mCallback = callback;
                 return this;
             }
-
-            /**
-             * @param version Version number of the database (starting at 1); if the database is
-             * older,
-             * {@link SupportSQLiteOpenHelper.Callback#onUpgrade(SupportSQLiteDatabase, int, int)}
-             * will be used to upgrade the database; if the database is newer,
-             * {@link SupportSQLiteOpenHelper.Callback#onDowngrade(SupportSQLiteDatabase, int, int)}
-             * will be used to downgrade the database.
-             * @return this
-             */
-            public Builder version(int version) {
-                mVersion = version;
-                return this;
-            }
         }
     }
 
diff --git a/android/arch/persistence/db/framework/FrameworkSQLiteDatabase.java b/android/arch/persistence/db/framework/FrameworkSQLiteDatabase.java
index 92a5820..e9c2b74 100644
--- a/android/arch/persistence/db/framework/FrameworkSQLiteDatabase.java
+++ b/android/arch/persistence/db/framework/FrameworkSQLiteDatabase.java
@@ -53,8 +53,7 @@
      *
      * @param delegate The delegate to receive all calls.
      */
-    @SuppressWarnings("WeakerAccess")
-    public FrameworkSQLiteDatabase(SQLiteDatabase delegate) {
+    FrameworkSQLiteDatabase(SQLiteDatabase delegate) {
         mDelegate = delegate;
     }
 
diff --git a/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelper.java b/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelper.java
index aa08fa4..a1690f4 100644
--- a/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelper.java
+++ b/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelper.java
@@ -28,42 +28,14 @@
 class FrameworkSQLiteOpenHelper implements SupportSQLiteOpenHelper {
     private final OpenHelper mDelegate;
 
-    FrameworkSQLiteOpenHelper(Context context, String name, int version,
-            DatabaseErrorHandler errorHandler,
-            SupportSQLiteOpenHelper.Callback callback) {
-        mDelegate = createDelegate(context, name, version, errorHandler, callback);
+    FrameworkSQLiteOpenHelper(Context context, String name,
+            Callback callback) {
+        mDelegate = createDelegate(context, name, callback);
     }
 
-    private OpenHelper createDelegate(Context context, String name,
-            int version, DatabaseErrorHandler errorHandler,
-            final Callback callback) {
-        return new OpenHelper(context, name, null, version, errorHandler) {
-            @Override
-            public void onCreate(SQLiteDatabase sqLiteDatabase) {
-                mWrappedDb = new FrameworkSQLiteDatabase(sqLiteDatabase);
-                callback.onCreate(mWrappedDb);
-            }
-
-            @Override
-            public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
-                callback.onUpgrade(getWrappedDb(sqLiteDatabase), oldVersion, newVersion);
-            }
-
-            @Override
-            public void onConfigure(SQLiteDatabase db) {
-                callback.onConfigure(getWrappedDb(db));
-            }
-
-            @Override
-            public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
-                callback.onDowngrade(getWrappedDb(db), oldVersion, newVersion);
-            }
-
-            @Override
-            public void onOpen(SQLiteDatabase db) {
-                callback.onOpen(getWrappedDb(db));
-            }
-        };
+    private OpenHelper createDelegate(Context context, String name, Callback callback) {
+        final FrameworkSQLiteDatabase[] dbRef = new FrameworkSQLiteDatabase[1];
+        return new OpenHelper(context, name, dbRef, callback);
     }
 
     @Override
@@ -92,14 +64,29 @@
         mDelegate.close();
     }
 
-    abstract static class OpenHelper extends SQLiteOpenHelper {
+    static class OpenHelper extends SQLiteOpenHelper {
+        /**
+         * This is used as an Object reference so that we can access the wrapped database inside
+         * the constructor. SQLiteOpenHelper requires the error handler to be passed in the
+         * constructor.
+         */
+        final FrameworkSQLiteDatabase[] mDbRef;
+        final Callback mCallback;
 
-        FrameworkSQLiteDatabase mWrappedDb;
-
-        OpenHelper(Context context, String name,
-                SQLiteDatabase.CursorFactory factory, int version,
-                DatabaseErrorHandler errorHandler) {
-            super(context, name, factory, version, errorHandler);
+        OpenHelper(Context context, String name, final FrameworkSQLiteDatabase[] dbRef,
+                final Callback callback) {
+            super(context, name, null, callback.version,
+                    new DatabaseErrorHandler() {
+                        @Override
+                        public void onCorruption(SQLiteDatabase dbObj) {
+                            FrameworkSQLiteDatabase db = dbRef[0];
+                            if (db != null) {
+                                callback.onCorruption(db);
+                            }
+                        }
+                    });
+            mCallback = callback;
+            mDbRef = dbRef;
         }
 
         SupportSQLiteDatabase getWritableSupportDatabase() {
@@ -113,16 +100,43 @@
         }
 
         FrameworkSQLiteDatabase getWrappedDb(SQLiteDatabase sqLiteDatabase) {
-            if (mWrappedDb == null) {
-                mWrappedDb = new FrameworkSQLiteDatabase(sqLiteDatabase);
+            FrameworkSQLiteDatabase dbRef = mDbRef[0];
+            if (dbRef == null) {
+                dbRef = new FrameworkSQLiteDatabase(sqLiteDatabase);
+                mDbRef[0] = dbRef;
             }
-            return mWrappedDb;
+            return mDbRef[0];
+        }
+
+        @Override
+        public void onCreate(SQLiteDatabase sqLiteDatabase) {
+            mCallback.onCreate(getWrappedDb(sqLiteDatabase));
+        }
+
+        @Override
+        public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
+            mCallback.onUpgrade(getWrappedDb(sqLiteDatabase), oldVersion, newVersion);
+        }
+
+        @Override
+        public void onConfigure(SQLiteDatabase db) {
+            mCallback.onConfigure(getWrappedDb(db));
+        }
+
+        @Override
+        public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+            mCallback.onDowngrade(getWrappedDb(db), oldVersion, newVersion);
+        }
+
+        @Override
+        public void onOpen(SQLiteDatabase db) {
+            mCallback.onOpen(getWrappedDb(db));
         }
 
         @Override
         public synchronized void close() {
             super.close();
-            mWrappedDb = null;
+            mDbRef[0] = null;
         }
     }
 }
diff --git a/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelperFactory.java b/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelperFactory.java
index 2268f45..ab11d49 100644
--- a/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelperFactory.java
+++ b/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelperFactory.java
@@ -27,8 +27,6 @@
     @Override
     public SupportSQLiteOpenHelper create(SupportSQLiteOpenHelper.Configuration configuration) {
         return new FrameworkSQLiteOpenHelper(
-                configuration.context, configuration.name,
-                configuration.version, configuration.errorHandler, configuration.callback
-        );
+                configuration.context, configuration.name, configuration.callback);
     }
 }
diff --git a/android/arch/persistence/db/framework/FrameworkSQLiteStatement.java b/android/arch/persistence/db/framework/FrameworkSQLiteStatement.java
index a2daf12..53a04bd 100644
--- a/android/arch/persistence/db/framework/FrameworkSQLiteStatement.java
+++ b/android/arch/persistence/db/framework/FrameworkSQLiteStatement.java
@@ -30,8 +30,7 @@
      *
      * @param delegate The SQLiteStatement to delegate calls to.
      */
-    @SuppressWarnings("WeakerAccess")
-    public FrameworkSQLiteStatement(SQLiteStatement delegate) {
+    FrameworkSQLiteStatement(SQLiteStatement delegate) {
         mDelegate = delegate;
     }
 
diff --git a/android/arch/persistence/room/Entity.java b/android/arch/persistence/room/Entity.java
index f54f0f8..94ca3bf 100644
--- a/android/arch/persistence/room/Entity.java
+++ b/android/arch/persistence/room/Entity.java
@@ -36,6 +36,9 @@
  * When a class is marked as an Entity, all of its fields are persisted. If you would like to
  * exclude some of its fields, you can mark them with {@link Ignore}.
  * <p>
+ * If a field is {@code transient}, it is automatically ignored <b>unless</b> it is annotated with
+ * {@link ColumnInfo}, {@link Embedded} or {@link Relation}.
+ * <p>
  * Example:
  * <pre>
  * {@literal @}Entity
diff --git a/android/arch/persistence/room/ForeignKey.java b/android/arch/persistence/room/ForeignKey.java
index 4ba0fb3..3ba632b 100644
--- a/android/arch/persistence/room/ForeignKey.java
+++ b/android/arch/persistence/room/ForeignKey.java
@@ -40,7 +40,7 @@
  * <a href="https://sqlite.org/pragma.html#pragma_defer_foreign_keys">defer_foreign_keys</a> PRAGMA
  * to defer them depending on your transaction.
  * <p>
- * Please refer to the SQLite <a href="https://sqlite.org/foreignkeys.html>foreign keys</a>
+ * Please refer to the SQLite <a href="https://sqlite.org/foreignkeys.html">foreign keys</a>
  * documentation for details.
  */
 public @interface ForeignKey {
diff --git a/android/arch/persistence/room/InvalidationTracker.java b/android/arch/persistence/room/InvalidationTracker.java
index 33bc4ed..45ec028 100644
--- a/android/arch/persistence/room/InvalidationTracker.java
+++ b/android/arch/persistence/room/InvalidationTracker.java
@@ -16,7 +16,7 @@
 
 package android.arch.persistence.room;
 
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
 import android.arch.core.internal.SafeIterableMap;
 import android.arch.persistence.db.SupportSQLiteDatabase;
 import android.arch.persistence.db.SupportSQLiteStatement;
@@ -166,10 +166,12 @@
 
     private static void appendTriggerName(StringBuilder builder, String tableName,
             String triggerType) {
-        builder.append("room_table_modification_trigger_")
+        builder.append("`")
+                .append("room_table_modification_trigger_")
                 .append(tableName)
                 .append("_")
-                .append(triggerType);
+                .append(triggerType)
+                .append("`");
     }
 
     private void stopTrackingTable(SupportSQLiteDatabase writableDb, int tableId) {
@@ -192,9 +194,9 @@
             appendTriggerName(stringBuilder, tableName, trigger);
             stringBuilder.append(" AFTER ")
                     .append(trigger)
-                    .append(" ON ")
+                    .append(" ON `")
                     .append(tableName)
-                    .append(" BEGIN INSERT OR REPLACE INTO ")
+                    .append("` BEGIN INSERT OR REPLACE INTO ")
                     .append(UPDATE_TABLE_NAME)
                     .append(" VALUES(null, ")
                     .append(tableId)
@@ -238,7 +240,7 @@
             currentObserver = mObserverMap.putIfAbsent(observer, wrapper);
         }
         if (currentObserver == null && mObservedTableTracker.onAdded(tableIds)) {
-            AppToolkitTaskExecutor.getInstance().executeOnDiskIO(mSyncTriggers);
+            ArchTaskExecutor.getInstance().executeOnDiskIO(mSyncTriggers);
         }
     }
 
@@ -269,7 +271,7 @@
             wrapper = mObserverMap.remove(observer);
         }
         if (wrapper != null && mObservedTableTracker.onRemoved(wrapper.mTableIds)) {
-            AppToolkitTaskExecutor.getInstance().executeOnDiskIO(mSyncTriggers);
+            ArchTaskExecutor.getInstance().executeOnDiskIO(mSyncTriggers);
         }
     }
 
@@ -350,11 +352,18 @@
                     return;
                 }
 
-                if (mDatabase.inTransaction()
-                        || !mPendingRefresh.compareAndSet(true, false)) {
+                if (!mPendingRefresh.compareAndSet(true, false)) {
                     // no pending refresh
                     return;
                 }
+
+                if (mDatabase.inTransaction()) {
+                    // current thread is in a transaction. when it ends, it will invoke
+                    // refreshRunnable again. mPendingRefresh is left as false on purpose
+                    // so that the last transaction can flip it on again.
+                    return;
+                }
+
                 mCleanupStatement.executeUpdateDelete();
                 mQueryArgs[0] = mMaxVersion;
                 Cursor cursor = mDatabase.query(SELECT_UPDATED_TABLES_SQL, mQueryArgs);
@@ -400,7 +409,7 @@
     public void refreshVersionsAsync() {
         // TODO we should consider doing this sync instead of async.
         if (mPendingRefresh.compareAndSet(false, true)) {
-            AppToolkitTaskExecutor.getInstance().executeOnDiskIO(mRefreshRunnable);
+            ArchTaskExecutor.getInstance().executeOnDiskIO(mRefreshRunnable);
         }
     }
 
diff --git a/android/arch/persistence/room/InvalidationTrackerTest.java b/android/arch/persistence/room/InvalidationTrackerTest.java
index f0b730a..d7474fd 100644
--- a/android/arch/persistence/room/InvalidationTrackerTest.java
+++ b/android/arch/persistence/room/InvalidationTrackerTest.java
@@ -247,7 +247,7 @@
         mTracker.mRefreshRunnable.run();
     }
 
-    @Test
+    // @Test - disabled due to flakiness b/65257997
     public void closedDbAfterOpen() throws InterruptedException {
         setVersions(3, 1);
         mTracker.addObserver(new LatchObserver(1, "a", "b"));
diff --git a/android/arch/persistence/room/RoomDatabase.java b/android/arch/persistence/room/RoomDatabase.java
index e64f2d6..cdad868 100644
--- a/android/arch/persistence/room/RoomDatabase.java
+++ b/android/arch/persistence/room/RoomDatabase.java
@@ -16,7 +16,7 @@
 
 package android.arch.persistence.room;
 
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
 import android.arch.persistence.db.SimpleSQLiteQuery;
 import android.arch.persistence.db.SupportSQLiteDatabase;
 import android.arch.persistence.db.SupportSQLiteOpenHelper;
@@ -158,7 +158,7 @@
         if (mAllowMainThreadQueries) {
             return;
         }
-        if (AppToolkitTaskExecutor.getInstance().isMainThread()) {
+        if (ArchTaskExecutor.getInstance().isMainThread()) {
             throw new IllegalStateException("Cannot access database on the main thread since"
                     + " it may potentially lock the UI for a long period of time.");
         }
@@ -216,7 +216,11 @@
      */
     public void endTransaction() {
         mOpenHelper.getWritableDatabase().endTransaction();
-        mInvalidationTracker.refreshVersionsAsync();
+        if (!inTransaction()) {
+            // enqueue refresh only if we are NOT in a transaction. Otherwise, wait for the last
+            // endTransaction call to do it.
+            mInvalidationTracker.refreshVersionsAsync();
+        }
     }
 
     /**
@@ -311,7 +315,6 @@
         private ArrayList<Callback> mCallbacks;
 
         private SupportSQLiteOpenHelper.Factory mFactory;
-        private boolean mInMemory;
         private boolean mAllowMainThreadQueries;
         private boolean mRequireMigration;
         /**
@@ -381,6 +384,9 @@
         }
 
         /**
+         * Allows Room to destructively recreate database tables if {@link Migration}s that would
+         * migrate old database schemas to the latest schema version are not found.
+         * <p>
          * When the database version on the device does not match the latest schema version, Room
          * runs necessary {@link Migration}s on the database.
          * <p>
diff --git a/android/arch/persistence/room/RoomOpenHelper.java b/android/arch/persistence/room/RoomOpenHelper.java
index 8767f06..47279d6 100644
--- a/android/arch/persistence/room/RoomOpenHelper.java
+++ b/android/arch/persistence/room/RoomOpenHelper.java
@@ -44,6 +44,7 @@
 
     public RoomOpenHelper(@NonNull DatabaseConfiguration configuration, @NonNull Delegate delegate,
             @NonNull String identityHash) {
+        super(delegate.version);
         mConfiguration = configuration;
         mDelegate = delegate;
         mIdentityHash = identityHash;
@@ -135,6 +136,12 @@
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     public abstract static class Delegate {
+        public final int version;
+
+        public Delegate(int version) {
+            this.version = version;
+        }
+
         protected abstract void dropAllTables(SupportSQLiteDatabase database);
 
         protected abstract void createAllTables(SupportSQLiteDatabase database);
diff --git a/android/arch/persistence/room/RoomWarnings.java b/android/arch/persistence/room/RoomWarnings.java
index 91f32e4..c64be96 100644
--- a/android/arch/persistence/room/RoomWarnings.java
+++ b/android/arch/persistence/room/RoomWarnings.java
@@ -117,4 +117,12 @@
      */
     public static final String MISSING_INDEX_ON_FOREIGN_KEY_CHILD =
             "ROOM_MISSING_FOREIGN_KEY_CHILD_INDEX";
+
+    /**
+     * Reported when a Pojo has multiple constructors, one of which is a no-arg constructor. Room
+     * will pick that one by default but will print this warning in case the constructor choice is
+     * important. You can always guide Room to use the right constructor using the @Ignore
+     * annotation.
+     */
+    public static final String DEFAULT_CONSTRUCTOR = "ROOM_DEFAULT_CONSTRUCTOR";
 }
diff --git a/android/arch/persistence/room/RxRoom.java b/android/arch/persistence/room/RxRoom.java
index adfca27..285b3f8 100644
--- a/android/arch/persistence/room/RxRoom.java
+++ b/android/arch/persistence/room/RxRoom.java
@@ -16,7 +16,7 @@
 
 package android.arch.persistence.room;
 
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
 import android.support.annotation.Nullable;
 import android.support.annotation.RestrictTo;
 
@@ -133,7 +133,7 @@
                 public Disposable schedule(@NonNull Runnable run, long delay,
                         @NonNull TimeUnit unit) {
                     DisposableRunnable disposable = new DisposableRunnable(run, mDisposed);
-                    AppToolkitTaskExecutor.getInstance().executeOnDiskIO(run);
+                    ArchTaskExecutor.getInstance().executeOnDiskIO(run);
                     return disposable;
                 }
 
diff --git a/android/arch/persistence/room/Transaction.java b/android/arch/persistence/room/Transaction.java
new file mode 100644
index 0000000..914e4f4
--- /dev/null
+++ b/android/arch/persistence/room/Transaction.java
@@ -0,0 +1,51 @@
+/*
+ * 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.persistence.room;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks a method in an abstract {@link Dao} class as a transaction method.
+ * <p>
+ * The derived implementation of the method will execute the super method in a database transaction.
+ * All the parameters and return types are preserved. The transaction will be marked as successful
+ * unless an exception is thrown in the method body.
+ * <p>
+ * Example:
+ * <pre>
+ * {@literal @}Dao
+ * public abstract class ProductDao {
+ *    {@literal @}Insert
+ *     public abstract void insert(Product product);
+ *    {@literal @}Delete
+ *     public abstract void delete(Product product);
+ *    {@literal @}Transaction
+ *     public void insertAndDeleteInTransaction(Product newProduct, Product oldProduct) {
+ *         // Anything inside this method runs in a single transaction.
+ *         insert(newProduct);
+ *         delete(oldProduct);
+ *     }
+ * }
+ * </pre>
+ */
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.CLASS)
+public @interface Transaction {
+}
diff --git a/android/arch/persistence/room/integration/testapp/CustomerViewModel.java b/android/arch/persistence/room/integration/testapp/CustomerViewModel.java
index 1f434ad..320b2cd 100644
--- a/android/arch/persistence/room/integration/testapp/CustomerViewModel.java
+++ b/android/arch/persistence/room/integration/testapp/CustomerViewModel.java
@@ -17,7 +17,7 @@
 package android.arch.persistence.room.integration.testapp;
 
 import android.app.Application;
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
 import android.arch.lifecycle.AndroidViewModel;
 import android.arch.lifecycle.LiveData;
 import android.arch.paging.DataSource;
@@ -47,7 +47,7 @@
         mDatabase = Room.databaseBuilder(this.getApplication(),
                 SampleDatabase.class, "customerDatabase").build();
 
-        AppToolkitTaskExecutor.getInstance().executeOnDiskIO(new Runnable() {
+        ArchTaskExecutor.getInstance().executeOnDiskIO(new Runnable() {
             @Override
             public void run() {
                 // fill with some simple data
@@ -73,7 +73,7 @@
     }
 
     void insertCustomer() {
-        AppToolkitTaskExecutor.getInstance().executeOnDiskIO(new Runnable() {
+        ArchTaskExecutor.getInstance().executeOnDiskIO(new Runnable() {
             @Override
             public void run() {
                 mDatabase.getCustomerDao().insert(createCustomer());
diff --git a/android/arch/persistence/room/integration/testapp/PKeyTestDatabase.java b/android/arch/persistence/room/integration/testapp/PKeyTestDatabase.java
index e61d808..63b9507 100644
--- a/android/arch/persistence/room/integration/testapp/PKeyTestDatabase.java
+++ b/android/arch/persistence/room/integration/testapp/PKeyTestDatabase.java
@@ -23,13 +23,18 @@
 import android.arch.persistence.room.RoomDatabase;
 import android.arch.persistence.room.integration.testapp.vo.IntAutoIncPKeyEntity;
 import android.arch.persistence.room.integration.testapp.vo.IntegerAutoIncPKeyEntity;
+import android.arch.persistence.room.integration.testapp.vo.IntegerPKeyEntity;
+import android.arch.persistence.room.integration.testapp.vo.ObjectPKeyEntity;
 
 import java.util.List;
 
-@Database(entities = {IntAutoIncPKeyEntity.class, IntegerAutoIncPKeyEntity.class}, version = 1,
+@Database(entities = {IntAutoIncPKeyEntity.class, IntegerAutoIncPKeyEntity.class,
+        ObjectPKeyEntity.class, IntegerPKeyEntity.class}, version = 1,
         exportSchema = false)
 public abstract class PKeyTestDatabase extends RoomDatabase {
     public abstract IntPKeyDao intPKeyDao();
+    public abstract IntegerAutoIncPKeyDao integerAutoIncPKeyDao();
+    public abstract ObjectPKeyDao objectPKeyDao();
     public abstract IntegerPKeyDao integerPKeyDao();
 
     @Dao
@@ -50,9 +55,10 @@
     }
 
     @Dao
-    public interface IntegerPKeyDao {
+    public interface IntegerAutoIncPKeyDao {
         @Insert
-        void insertMe(IntegerAutoIncPKeyEntity items);
+        void insertMe(IntegerAutoIncPKeyEntity item);
+
         @Query("select * from IntegerAutoIncPKeyEntity WHERE pKey = :key")
         IntegerAutoIncPKeyEntity getMe(int key);
 
@@ -65,4 +71,19 @@
         @Query("select data from IntegerAutoIncPKeyEntity WHERE pKey IN(:ids)")
         List<String> loadDataById(long... ids);
     }
+
+    @Dao
+    public interface ObjectPKeyDao {
+        @Insert
+        void insertMe(ObjectPKeyEntity item);
+    }
+
+    @Dao
+    public interface IntegerPKeyDao {
+        @Insert
+        void insertMe(IntegerPKeyEntity item);
+
+        @Query("select * from IntegerPKeyEntity")
+        List<IntegerPKeyEntity> loadAll();
+    }
 }
diff --git a/android/arch/persistence/room/integration/testapp/TestDatabase.java b/android/arch/persistence/room/integration/testapp/TestDatabase.java
index 9417296..2fad7b1 100644
--- a/android/arch/persistence/room/integration/testapp/TestDatabase.java
+++ b/android/arch/persistence/room/integration/testapp/TestDatabase.java
@@ -21,6 +21,7 @@
 import android.arch.persistence.room.TypeConverter;
 import android.arch.persistence.room.TypeConverters;
 import android.arch.persistence.room.integration.testapp.dao.BlobEntityDao;
+import android.arch.persistence.room.integration.testapp.dao.FunnyNamedDao;
 import android.arch.persistence.room.integration.testapp.dao.PetCoupleDao;
 import android.arch.persistence.room.integration.testapp.dao.PetDao;
 import android.arch.persistence.room.integration.testapp.dao.ProductDao;
@@ -31,6 +32,7 @@
 import android.arch.persistence.room.integration.testapp.dao.UserPetDao;
 import android.arch.persistence.room.integration.testapp.dao.WithClauseDao;
 import android.arch.persistence.room.integration.testapp.vo.BlobEntity;
+import android.arch.persistence.room.integration.testapp.vo.FunnyNamedEntity;
 import android.arch.persistence.room.integration.testapp.vo.Pet;
 import android.arch.persistence.room.integration.testapp.vo.PetCouple;
 import android.arch.persistence.room.integration.testapp.vo.Product;
@@ -41,7 +43,7 @@
 import java.util.Date;
 
 @Database(entities = {User.class, Pet.class, School.class, PetCouple.class, Toy.class,
-        BlobEntity.class, Product.class},
+        BlobEntity.class, Product.class, FunnyNamedEntity.class},
         version = 1, exportSchema = false)
 @TypeConverters(TestDatabase.Converters.class)
 public abstract class TestDatabase extends RoomDatabase {
@@ -55,6 +57,7 @@
     public abstract ProductDao getProductDao();
     public abstract SpecificDogDao getSpecificDogDao();
     public abstract WithClauseDao getWithClauseDao();
+    public abstract FunnyNamedDao getFunnyNamedDao();
 
     @SuppressWarnings("unused")
     public static class Converters {
diff --git a/android/arch/persistence/room/integration/testapp/dao/FunnyNamedDao.java b/android/arch/persistence/room/integration/testapp/dao/FunnyNamedDao.java
new file mode 100644
index 0000000..93b5e72
--- /dev/null
+++ b/android/arch/persistence/room/integration/testapp/dao/FunnyNamedDao.java
@@ -0,0 +1,50 @@
+/*
+ * 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.persistence.room.integration.testapp.dao;
+
+import static android.arch.persistence.room.integration.testapp.vo.FunnyNamedEntity.COLUMN_ID;
+import static android.arch.persistence.room.integration.testapp.vo.FunnyNamedEntity.TABLE_NAME;
+
+import android.arch.lifecycle.LiveData;
+import android.arch.persistence.room.Dao;
+import android.arch.persistence.room.Delete;
+import android.arch.persistence.room.Insert;
+import android.arch.persistence.room.Query;
+import android.arch.persistence.room.Update;
+import android.arch.persistence.room.integration.testapp.vo.FunnyNamedEntity;
+
+import java.util.List;
+
+@Dao
+public interface FunnyNamedDao {
+    String SELECT_ONE = "select * from \"" +  TABLE_NAME + "\" WHERE \"" + COLUMN_ID + "\" = :id";
+    @Insert
+    void insert(FunnyNamedEntity... entities);
+    @Delete
+    void delete(FunnyNamedEntity... entities);
+    @Update
+    void update(FunnyNamedEntity... entities);
+
+    @Query("select * from \"" +  TABLE_NAME + "\" WHERE \"" + COLUMN_ID + "\" IN (:ids)")
+    List<FunnyNamedEntity> loadAll(int... ids);
+
+    @Query(SELECT_ONE)
+    LiveData<FunnyNamedEntity> observableOne(int id);
+
+    @Query(SELECT_ONE)
+    FunnyNamedEntity load(int id);
+}
diff --git a/android/arch/persistence/room/integration/testapp/dao/SchoolDao.java b/android/arch/persistence/room/integration/testapp/dao/SchoolDao.java
index 7bb137f..18e8d93 100644
--- a/android/arch/persistence/room/integration/testapp/dao/SchoolDao.java
+++ b/android/arch/persistence/room/integration/testapp/dao/SchoolDao.java
@@ -35,16 +35,16 @@
     @Query("SELECT * from School WHERE address_street LIKE '%' || :street || '%'")
     public abstract List<School> findByStreet(String street);
 
-    @Query("SELECT mName, manager_mName FROM School")
+    @Query("SELECT mId, mName, manager_mName FROM School")
     public abstract List<School> schoolAndManagerNames();
 
-    @Query("SELECT mName, manager_mName FROM School")
+    @Query("SELECT mId, mName, manager_mName FROM School")
     @SuppressWarnings(RoomWarnings.CURSOR_MISMATCH)
     public abstract List<SchoolRef> schoolAndManagerNamesAsPojo();
 
     @Query("SELECT address_lat as lat, address_lng as lng FROM School WHERE mId = :schoolId")
     public abstract Coordinates loadCoordinates(int schoolId);
 
-    @Query("SELECT address_lat, address_lng FROM School WHERE mId = :schoolId")
+    @Query("SELECT mId, address_lat, address_lng FROM School WHERE mId = :schoolId")
     public abstract School loadCoordinatesAsSchool(int schoolId);
 }
diff --git a/android/arch/persistence/room/integration/testapp/dao/UserDao.java b/android/arch/persistence/room/integration/testapp/dao/UserDao.java
index 337c233..665a1ae 100644
--- a/android/arch/persistence/room/integration/testapp/dao/UserDao.java
+++ b/android/arch/persistence/room/integration/testapp/dao/UserDao.java
@@ -24,6 +24,7 @@
 import android.arch.persistence.room.Insert;
 import android.arch.persistence.room.OnConflictStrategy;
 import android.arch.persistence.room.Query;
+import android.arch.persistence.room.Transaction;
 import android.arch.persistence.room.Update;
 import android.arch.persistence.room.integration.testapp.TestDatabase;
 import android.arch.persistence.room.integration.testapp.vo.AvgWeightByAge;
@@ -259,4 +260,10 @@
             + " WHERE mLastName > :lastName or (mLastName = :lastName and (mName < :name or (mName = :name and mId > :id)))"
             + " ORDER BY mLastName ASC, mName DESC, mId ASC")
     public abstract int userComplexCountBefore(String lastName, String name, int id);
+
+    @Transaction
+    public void insertBothByAnnotation(final User a, final User b) {
+        insert(a);
+        insert(b);
+    }
 }
diff --git a/android/arch/persistence/room/integration/testapp/dao/UserPetDao.java b/android/arch/persistence/room/integration/testapp/dao/UserPetDao.java
index 3507aee..eb15901 100644
--- a/android/arch/persistence/room/integration/testapp/dao/UserPetDao.java
+++ b/android/arch/persistence/room/integration/testapp/dao/UserPetDao.java
@@ -33,6 +33,8 @@
 
 import java.util.List;
 
+import io.reactivex.Flowable;
+
 @Dao
 public interface UserPetDao {
     @Query("SELECT * FROM User u, Pet p WHERE u.mId = p.mUserId")
@@ -62,6 +64,9 @@
     @Query("SELECT * FROM User u where u.mId = :userId")
     LiveData<UserAndAllPets> liveUserWithPets(int userId);
 
+    @Query("SELECT * FROM User u where u.mId = :userId")
+    Flowable<UserAndAllPets> flowableUserWithPets(int userId);
+
     @Query("SELECT * FROM User u where u.mId = :uid")
     EmbeddedUserAndAllPets loadUserAndPetsAsEmbedded(int uid);
 
diff --git a/android/arch/persistence/room/integration/testapp/dao/WithClauseDao.java b/android/arch/persistence/room/integration/testapp/dao/WithClauseDao.java
index b1c38ed..40098ed 100644
--- a/android/arch/persistence/room/integration/testapp/dao/WithClauseDao.java
+++ b/android/arch/persistence/room/integration/testapp/dao/WithClauseDao.java
@@ -16,13 +16,16 @@
 
 package android.arch.persistence.room.integration.testapp.dao;
 
+import android.annotation.TargetApi;
 import android.arch.lifecycle.LiveData;
 import android.arch.persistence.room.Dao;
 import android.arch.persistence.room.Query;
+import android.os.Build;
 
 import java.util.List;
 
 @Dao
+@TargetApi(Build.VERSION_CODES.LOLLIPOP)
 public interface WithClauseDao {
     @Query("WITH RECURSIVE factorial(n, fact) AS \n"
             + "(SELECT 0, 1 \n"
diff --git a/android/arch/persistence/room/integration/testapp/database/SampleDatabase.java b/android/arch/persistence/room/integration/testapp/database/SampleDatabase.java
index eec59f6..9020eb1 100644
--- a/android/arch/persistence/room/integration/testapp/database/SampleDatabase.java
+++ b/android/arch/persistence/room/integration/testapp/database/SampleDatabase.java
@@ -18,10 +18,6 @@
 
 import android.arch.persistence.room.Database;
 import android.arch.persistence.room.RoomDatabase;
-import android.arch.persistence.room.TypeConverter;
-import android.arch.persistence.room.TypeConverters;
-
-import java.util.Date;
 
 /**
  * Sample database of customers.
diff --git a/android/arch/persistence/room/integration/testapp/migration/MigrationTest.java b/android/arch/persistence/room/integration/testapp/migration/MigrationTest.java
index 725d53f..7fe2bc9 100644
--- a/android/arch/persistence/room/integration/testapp/migration/MigrationTest.java
+++ b/android/arch/persistence/room/integration/testapp/migration/MigrationTest.java
@@ -318,6 +318,8 @@
                     + " (`id` INTEGER NOT NULL, `name` TEXT COLLATE NOCASE, PRIMARY KEY(`id`),"
                     + " FOREIGN KEY(`name`) REFERENCES `Entity1`(`name`)"
                     + " ON UPDATE NO ACTION ON DELETE NO ACTION DEFERRABLE INITIALLY DEFERRED)");
+            database.execSQL("CREATE UNIQUE INDEX `index_entity1` ON "
+                    + MigrationDb.Entity1.TABLE_NAME + " (`name`)");
         }
     };
 
diff --git a/android/arch/persistence/room/integration/testapp/paging/LivePagedListProviderTest.java b/android/arch/persistence/room/integration/testapp/paging/LivePagedListProviderTest.java
index 4c9d73e..df70a17 100644
--- a/android/arch/persistence/room/integration/testapp/paging/LivePagedListProviderTest.java
+++ b/android/arch/persistence/room/integration/testapp/paging/LivePagedListProviderTest.java
@@ -21,17 +21,17 @@
 import static org.hamcrest.CoreMatchers.nullValue;
 import static org.hamcrest.MatcherAssert.assertThat;
 
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
 import android.arch.core.executor.testing.CountingTaskExecutorRule;
 import android.arch.lifecycle.Lifecycle;
 import android.arch.lifecycle.LifecycleOwner;
 import android.arch.lifecycle.LifecycleRegistry;
 import android.arch.lifecycle.LiveData;
 import android.arch.lifecycle.Observer;
+import android.arch.paging.PagedList;
 import android.arch.persistence.room.integration.testapp.test.TestDatabaseTest;
 import android.arch.persistence.room.integration.testapp.test.TestUtil;
 import android.arch.persistence.room.integration.testapp.vo.User;
-import android.arch.paging.PagedList;
 import android.support.annotation.Nullable;
 import android.support.test.filters.LargeTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -131,7 +131,7 @@
                 return null;
             }
         });
-        AppToolkitTaskExecutor.getInstance().executeOnMainThread(futureTask);
+        ArchTaskExecutor.getInstance().executeOnMainThread(futureTask);
         futureTask.get();
     }
 
@@ -155,7 +155,7 @@
 
     private static class PagedListObserver<T> implements Observer<PagedList<T>> {
         private PagedList<T> mList;
-        public void reset() {
+        void reset() {
             mList = null;
         }
 
diff --git a/android/arch/persistence/room/integration/testapp/test/CustomDatabaseTest.java b/android/arch/persistence/room/integration/testapp/test/CustomDatabaseTest.java
index 353c2e3..6f44546 100644
--- a/android/arch/persistence/room/integration/testapp/test/CustomDatabaseTest.java
+++ b/android/arch/persistence/room/integration/testapp/test/CustomDatabaseTest.java
@@ -55,7 +55,6 @@
         Customer customer = new Customer();
         for (int i = 0; i < 100; i++) {
             SampleDatabase db = builder.build();
-            customer.setId(i);
             db.getCustomerDao().insert(customer);
             // Give InvalidationTracker enough time to start #mRefreshRunnable and pass the
             // initialization check.
diff --git a/android/arch/persistence/room/integration/testapp/test/FunnyNamedDaoTest.java b/android/arch/persistence/room/integration/testapp/test/FunnyNamedDaoTest.java
new file mode 100644
index 0000000..f4fca7f
--- /dev/null
+++ b/android/arch/persistence/room/integration/testapp/test/FunnyNamedDaoTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.persistence.room.integration.testapp.test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.arch.core.executor.testing.CountingTaskExecutorRule;
+import android.arch.lifecycle.Observer;
+import android.arch.persistence.room.integration.testapp.vo.FunnyNamedEntity;
+import android.support.annotation.Nullable;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class FunnyNamedDaoTest extends TestDatabaseTest {
+    @Rule
+    public CountingTaskExecutorRule mExecutorRule = new CountingTaskExecutorRule();
+
+    @Test
+    public void readWrite() {
+        FunnyNamedEntity entity = new FunnyNamedEntity(1, "a");
+        mFunnyNamedDao.insert(entity);
+        FunnyNamedEntity loaded = mFunnyNamedDao.load(1);
+        assertThat(loaded, is(entity));
+    }
+
+    @Test
+    public void update() {
+        FunnyNamedEntity entity = new FunnyNamedEntity(1, "a");
+        mFunnyNamedDao.insert(entity);
+        entity.setValue("b");
+        mFunnyNamedDao.update(entity);
+        FunnyNamedEntity loaded = mFunnyNamedDao.load(1);
+        assertThat(loaded.getValue(), is("b"));
+    }
+
+    @Test
+    public void delete() {
+        FunnyNamedEntity entity = new FunnyNamedEntity(1, "a");
+        mFunnyNamedDao.insert(entity);
+        assertThat(mFunnyNamedDao.load(1), notNullValue());
+        mFunnyNamedDao.delete(entity);
+        assertThat(mFunnyNamedDao.load(1), nullValue());
+    }
+
+    @Test
+    public void observe() throws TimeoutException, InterruptedException {
+        final FunnyNamedEntity[] item = new FunnyNamedEntity[1];
+        mFunnyNamedDao.observableOne(2).observeForever(new Observer<FunnyNamedEntity>() {
+            @Override
+            public void onChanged(@Nullable FunnyNamedEntity funnyNamedEntity) {
+                item[0] = funnyNamedEntity;
+            }
+        });
+
+        FunnyNamedEntity entity = new FunnyNamedEntity(1, "a");
+        mFunnyNamedDao.insert(entity);
+        mExecutorRule.drainTasks(1, TimeUnit.MINUTES);
+        assertThat(item[0], nullValue());
+
+        final FunnyNamedEntity entity2 = new FunnyNamedEntity(2, "b");
+        mFunnyNamedDao.insert(entity2);
+        mExecutorRule.drainTasks(1, TimeUnit.MINUTES);
+        assertThat(item[0], is(entity2));
+
+        final FunnyNamedEntity entity3 = new FunnyNamedEntity(2, "c");
+        mFunnyNamedDao.update(entity3);
+        mExecutorRule.drainTasks(1, TimeUnit.MINUTES);
+        assertThat(item[0], is(entity3));
+    }
+}
diff --git a/android/arch/persistence/room/integration/testapp/test/InvalidationTest.java b/android/arch/persistence/room/integration/testapp/test/InvalidationTest.java
index 4787ce5..84f20ec 100644
--- a/android/arch/persistence/room/integration/testapp/test/InvalidationTest.java
+++ b/android/arch/persistence/room/integration/testapp/test/InvalidationTest.java
@@ -21,7 +21,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
 
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
 import android.arch.core.executor.TaskExecutor;
 import android.arch.persistence.room.InvalidationTracker;
 import android.arch.persistence.room.Room;
@@ -68,7 +68,7 @@
 
     @Before
     public void setSingleThreadedIO() {
-        AppToolkitTaskExecutor.getInstance().setDelegate(new TaskExecutor() {
+        ArchTaskExecutor.getInstance().setDelegate(new TaskExecutor() {
             ExecutorService mIOExecutor = Executors.newSingleThreadExecutor();
             Handler mHandler = new Handler(Looper.getMainLooper());
 
@@ -91,7 +91,7 @@
 
     @After
     public void clearExecutor() {
-        AppToolkitTaskExecutor.getInstance().setDelegate(null);
+        ArchTaskExecutor.getInstance().setDelegate(null);
     }
 
     private void waitUntilIOThreadIsIdle() {
@@ -101,7 +101,7 @@
                 return null;
             }
         });
-        AppToolkitTaskExecutor.getInstance().executeOnDiskIO(future);
+        ArchTaskExecutor.getInstance().executeOnDiskIO(future);
         //noinspection TryWithIdenticalCatches
         try {
             future.get();
diff --git a/android/arch/persistence/room/integration/testapp/test/LiveDataQueryTest.java b/android/arch/persistence/room/integration/testapp/test/LiveDataQueryTest.java
index cae8445..d78411f 100644
--- a/android/arch/persistence/room/integration/testapp/test/LiveDataQueryTest.java
+++ b/android/arch/persistence/room/integration/testapp/test/LiveDataQueryTest.java
@@ -21,7 +21,7 @@
 import static org.hamcrest.CoreMatchers.nullValue;
 import static org.hamcrest.MatcherAssert.assertThat;
 
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
 import android.arch.core.executor.testing.CountingTaskExecutorRule;
 import android.arch.lifecycle.Lifecycle;
 import android.arch.lifecycle.LifecycleOwner;
@@ -35,9 +35,11 @@
 import android.arch.persistence.room.integration.testapp.vo.Toy;
 import android.arch.persistence.room.integration.testapp.vo.User;
 import android.arch.persistence.room.integration.testapp.vo.UserAndAllPets;
+import android.os.Build;
 import android.support.annotation.Nullable;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.MediumTest;
+import android.support.test.filters.SdkSuppress;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
@@ -235,6 +237,7 @@
     }
 
     @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
     public void withWithClause() throws ExecutionException, InterruptedException,
             TimeoutException {
         LiveData<List<String>> actual =
@@ -322,7 +325,7 @@
                 return null;
             }
         });
-        AppToolkitTaskExecutor.getInstance().executeOnMainThread(futureTask);
+        ArchTaskExecutor.getInstance().executeOnMainThread(futureTask);
         futureTask.get();
     }
 
diff --git a/android/arch/persistence/room/integration/testapp/test/PrimaryKeyTest.java b/android/arch/persistence/room/integration/testapp/test/PrimaryKeyTest.java
index 97ce10c..fda4373 100644
--- a/android/arch/persistence/room/integration/testapp/test/PrimaryKeyTest.java
+++ b/android/arch/persistence/room/integration/testapp/test/PrimaryKeyTest.java
@@ -16,29 +16,35 @@
 
 package android.arch.persistence.room.integration.testapp.test;
 
+import static org.hamcrest.CoreMatchers.instanceOf;
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.notNullValue;
 import static org.hamcrest.MatcherAssert.assertThat;
-
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
+import static org.junit.Assert.assertNotNull;
 
 import android.arch.persistence.room.Room;
 import android.arch.persistence.room.integration.testapp.PKeyTestDatabase;
 import android.arch.persistence.room.integration.testapp.vo.IntAutoIncPKeyEntity;
 import android.arch.persistence.room.integration.testapp.vo.IntegerAutoIncPKeyEntity;
+import android.arch.persistence.room.integration.testapp.vo.IntegerPKeyEntity;
+import android.arch.persistence.room.integration.testapp.vo.ObjectPKeyEntity;
+import android.database.sqlite.SQLiteConstraintException;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.util.Arrays;
+import java.util.List;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class PrimaryKeyTest {
     private PKeyTestDatabase mDatabase;
+
     @Before
     public void setup() {
         mDatabase = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getTargetContext(),
@@ -49,8 +55,8 @@
     public void integerTest() {
         IntegerAutoIncPKeyEntity entity = new IntegerAutoIncPKeyEntity();
         entity.data = "foo";
-        mDatabase.integerPKeyDao().insertMe(entity);
-        IntegerAutoIncPKeyEntity loaded = mDatabase.integerPKeyDao().getMe(1);
+        mDatabase.integerAutoIncPKeyDao().insertMe(entity);
+        IntegerAutoIncPKeyEntity loaded = mDatabase.integerAutoIncPKeyDao().getMe(1);
         assertThat(loaded, notNullValue());
         assertThat(loaded.data, is(entity.data));
     }
@@ -60,8 +66,8 @@
         IntegerAutoIncPKeyEntity entity = new IntegerAutoIncPKeyEntity();
         entity.pKey = 0;
         entity.data = "foo";
-        mDatabase.integerPKeyDao().insertMe(entity);
-        IntegerAutoIncPKeyEntity loaded = mDatabase.integerPKeyDao().getMe(0);
+        mDatabase.integerAutoIncPKeyDao().insertMe(entity);
+        IntegerAutoIncPKeyEntity loaded = mDatabase.integerAutoIncPKeyDao().getMe(0);
         assertThat(loaded, notNullValue());
         assertThat(loaded.data, is(entity.data));
     }
@@ -98,8 +104,8 @@
     public void getInsertedIdFromInteger() {
         IntegerAutoIncPKeyEntity entity = new IntegerAutoIncPKeyEntity();
         entity.data = "foo";
-        final long id = mDatabase.integerPKeyDao().insertAndGetId(entity);
-        assertThat(mDatabase.integerPKeyDao().getMe((int) id).data, is("foo"));
+        final long id = mDatabase.integerAutoIncPKeyDao().insertAndGetId(entity);
+        assertThat(mDatabase.integerAutoIncPKeyDao().getMe((int) id).data, is("foo"));
     }
 
     @Test
@@ -108,7 +114,34 @@
         entity.data = "foo";
         IntegerAutoIncPKeyEntity entity2 = new IntegerAutoIncPKeyEntity();
         entity2.data = "foo2";
-        final long[] ids = mDatabase.integerPKeyDao().insertAndGetIds(entity, entity2);
-        assertThat(mDatabase.integerPKeyDao().loadDataById(ids), is(Arrays.asList("foo", "foo2")));
+        final long[] ids = mDatabase.integerAutoIncPKeyDao().insertAndGetIds(entity, entity2);
+        assertThat(mDatabase.integerAutoIncPKeyDao().loadDataById(ids),
+                is(Arrays.asList("foo", "foo2")));
+    }
+
+    @Test
+    public void insertNullPrimaryKey() throws Exception {
+        ObjectPKeyEntity o1 = new ObjectPKeyEntity(null, "1");
+
+        Throwable throwable = null;
+        try {
+            mDatabase.objectPKeyDao().insertMe(o1);
+        } catch (Throwable t) {
+            throwable = t;
+        }
+        assertNotNull("Was expecting an exception", throwable);
+        assertThat(throwable, instanceOf(SQLiteConstraintException.class));
+    }
+
+    @Test
+    public void insertNullPrimaryKeyForInteger() throws Exception {
+        IntegerPKeyEntity entity = new IntegerPKeyEntity();
+        entity.data = "data";
+        mDatabase.integerPKeyDao().insertMe(entity);
+
+        List<IntegerPKeyEntity> list = mDatabase.integerPKeyDao().loadAll();
+        assertThat(list.size(), is(1));
+        assertThat(list.get(0).data, is("data"));
+        assertNotNull(list.get(0).pKey);
     }
 }
diff --git a/android/arch/persistence/room/integration/testapp/test/RxJava2Test.java b/android/arch/persistence/room/integration/testapp/test/RxJava2Test.java
index 1bbc140..01d071e 100644
--- a/android/arch/persistence/room/integration/testapp/test/RxJava2Test.java
+++ b/android/arch/persistence/room/integration/testapp/test/RxJava2Test.java
@@ -19,10 +19,12 @@
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
 
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
 import android.arch.core.executor.TaskExecutor;
 import android.arch.persistence.room.EmptyResultSetException;
+import android.arch.persistence.room.integration.testapp.vo.Pet;
 import android.arch.persistence.room.integration.testapp.vo.User;
+import android.arch.persistence.room.integration.testapp.vo.UserAndAllPets;
 import android.support.test.filters.MediumTest;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -38,6 +40,7 @@
 import java.util.List;
 
 import io.reactivex.disposables.Disposable;
+import io.reactivex.functions.Predicate;
 import io.reactivex.observers.TestObserver;
 import io.reactivex.schedulers.TestScheduler;
 import io.reactivex.subscribers.TestSubscriber;
@@ -52,7 +55,7 @@
     public void setupSchedulers() {
         mTestScheduler = new TestScheduler();
         mTestScheduler.start();
-        AppToolkitTaskExecutor.getInstance().setDelegate(new TaskExecutor() {
+        ArchTaskExecutor.getInstance().setDelegate(new TaskExecutor() {
             @Override
             public void executeOnDiskIO(Runnable runnable) {
                 mTestScheduler.scheduleDirect(runnable);
@@ -73,7 +76,7 @@
     @After
     public void clearSchedulers() {
         mTestScheduler.shutdown();
-        AppToolkitTaskExecutor.getInstance().setDelegate(null);
+        ArchTaskExecutor.getInstance().setDelegate(null);
     }
 
     private void drain() throws InterruptedException {
@@ -269,4 +272,60 @@
         subscriber.cancel();
         subscriber.assertNoErrors();
     }
+
+    @Test
+    public void flowableWithRelation() throws InterruptedException {
+        final TestSubscriber<UserAndAllPets> subscriber = new TestSubscriber<>();
+
+        mUserPetDao.flowableUserWithPets(3).subscribe(subscriber);
+        drain();
+        subscriber.assertSubscribed();
+
+        drain();
+        subscriber.assertNoValues();
+
+        final User user = TestUtil.createUser(3);
+        mUserDao.insert(user);
+        drain();
+        subscriber.assertValue(new Predicate<UserAndAllPets>() {
+            @Override
+            public boolean test(UserAndAllPets userAndAllPets) throws Exception {
+                return userAndAllPets.user.equals(user);
+            }
+        });
+        subscriber.assertValueCount(1);
+        final Pet[] pets = TestUtil.createPetsForUser(3, 1, 2);
+        mPetDao.insertAll(pets);
+        drain();
+        subscriber.assertValueAt(1, new Predicate<UserAndAllPets>() {
+            @Override
+            public boolean test(UserAndAllPets userAndAllPets) throws Exception {
+                return userAndAllPets.user.equals(user)
+                        && userAndAllPets.pets.equals(Arrays.asList(pets));
+            }
+        });
+    }
+
+    @Test
+    public void flowable_updateInTransaction() throws InterruptedException {
+        // When subscribing to the emissions of the user
+        final TestSubscriber<User> userTestSubscriber = mUserDao
+                .flowableUserById(3)
+                .observeOn(mTestScheduler)
+                .test();
+        drain();
+        userTestSubscriber.assertValueCount(0);
+
+        // When inserting a new user in the data source
+        mDatabase.beginTransaction();
+        try {
+            mUserDao.insert(TestUtil.createUser(3));
+            mDatabase.setTransactionSuccessful();
+
+        } finally {
+            mDatabase.endTransaction();
+        }
+        drain();
+        userTestSubscriber.assertValueCount(1);
+    }
 }
diff --git a/android/arch/persistence/room/integration/testapp/test/RxJava2WithInstantTaskExecutorTest.java b/android/arch/persistence/room/integration/testapp/test/RxJava2WithInstantTaskExecutorTest.java
new file mode 100644
index 0000000..fcd0b00
--- /dev/null
+++ b/android/arch/persistence/room/integration/testapp/test/RxJava2WithInstantTaskExecutorTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.persistence.room.integration.testapp.test;
+
+import android.arch.core.executor.testing.InstantTaskExecutorRule;
+import android.arch.persistence.room.Room;
+import android.arch.persistence.room.integration.testapp.TestDatabase;
+import android.arch.persistence.room.integration.testapp.vo.User;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import io.reactivex.subscribers.TestSubscriber;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RxJava2WithInstantTaskExecutorTest {
+    @Rule
+    public InstantTaskExecutorRule instantTaskExecutorRule = new InstantTaskExecutorRule();
+
+    private TestDatabase mDatabase;
+
+    @Before
+    public void initDb() throws Exception {
+        // using an in-memory database because the information stored here disappears when the
+        // process is killed
+        mDatabase = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getContext(),
+                TestDatabase.class)
+                // allowing main thread queries, just for testing
+                .allowMainThreadQueries()
+                .build();
+    }
+
+    @Test
+    public void testFlowableInTransaction() {
+        // When subscribing to the emissions of the user
+        TestSubscriber<User> subscriber = mDatabase.getUserDao().flowableUserById(3).test();
+        subscriber.assertValueCount(0);
+
+        // When inserting a new user in the data source
+        mDatabase.beginTransaction();
+        try {
+            mDatabase.getUserDao().insert(TestUtil.createUser(3));
+            mDatabase.setTransactionSuccessful();
+        } finally {
+            mDatabase.endTransaction();
+        }
+
+        subscriber.assertValueCount(1);
+    }
+}
diff --git a/android/arch/persistence/room/integration/testapp/test/SimpleEntityReadWriteTest.java b/android/arch/persistence/room/integration/testapp/test/SimpleEntityReadWriteTest.java
index 8861adb..f8049f3 100644
--- a/android/arch/persistence/room/integration/testapp/test/SimpleEntityReadWriteTest.java
+++ b/android/arch/persistence/room/integration/testapp/test/SimpleEntityReadWriteTest.java
@@ -502,4 +502,26 @@
         assertThat(mUserDao.updateByAgeAndIds(3f, 30, Arrays.asList(3, 5)), is(1));
         assertThat(mUserDao.loadByIds(3)[0].getWeight(), is(3f));
     }
+
+    @Test
+    public void transactionByAnnotation() {
+        User a = TestUtil.createUser(3);
+        User b = TestUtil.createUser(5);
+        mUserDao.insertBothByAnnotation(a, b);
+        assertThat(mUserDao.count(), is(2));
+    }
+
+    @Test
+    public void transactionByAnnotation_failure() {
+        User a = TestUtil.createUser(3);
+        User b = TestUtil.createUser(3);
+        boolean caught = false;
+        try {
+            mUserDao.insertBothByAnnotation(a, b);
+        } catch (SQLiteConstraintException e) {
+            caught = true;
+        }
+        assertTrue("SQLiteConstraintException expected", caught);
+        assertThat(mUserDao.count(), is(0));
+    }
 }
diff --git a/android/arch/persistence/room/integration/testapp/test/TestDatabaseTest.java b/android/arch/persistence/room/integration/testapp/test/TestDatabaseTest.java
index 51d5bb3..ec77561 100644
--- a/android/arch/persistence/room/integration/testapp/test/TestDatabaseTest.java
+++ b/android/arch/persistence/room/integration/testapp/test/TestDatabaseTest.java
@@ -18,6 +18,7 @@
 
 import android.arch.persistence.room.Room;
 import android.arch.persistence.room.integration.testapp.TestDatabase;
+import android.arch.persistence.room.integration.testapp.dao.FunnyNamedDao;
 import android.arch.persistence.room.integration.testapp.dao.PetCoupleDao;
 import android.arch.persistence.room.integration.testapp.dao.PetDao;
 import android.arch.persistence.room.integration.testapp.dao.SchoolDao;
@@ -42,6 +43,7 @@
     protected ToyDao mToyDao;
     protected SpecificDogDao mSpecificDogDao;
     protected WithClauseDao mWithClauseDao;
+    protected FunnyNamedDao mFunnyNamedDao;
 
     @Before
     public void createDb() {
@@ -55,5 +57,6 @@
         mToyDao = mDatabase.getToyDao();
         mSpecificDogDao = mDatabase.getSpecificDogDao();
         mWithClauseDao = mDatabase.getWithClauseDao();
+        mFunnyNamedDao = mDatabase.getFunnyNamedDao();
     }
 }
diff --git a/android/arch/persistence/room/integration/testapp/test/WithClauseTest.java b/android/arch/persistence/room/integration/testapp/test/WithClauseTest.java
index 10897da..9209638 100644
--- a/android/arch/persistence/room/integration/testapp/test/WithClauseTest.java
+++ b/android/arch/persistence/room/integration/testapp/test/WithClauseTest.java
@@ -20,6 +20,8 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import android.arch.persistence.room.integration.testapp.vo.User;
+import android.os.Build;
+import android.support.test.filters.SdkSuppress;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
@@ -32,6 +34,7 @@
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
 public class WithClauseTest extends TestDatabaseTest{
     @Test
     public void noSourceOfData() {
diff --git a/android/arch/persistence/room/integration/testapp/vo/FunnyNamedEntity.java b/android/arch/persistence/room/integration/testapp/vo/FunnyNamedEntity.java
new file mode 100644
index 0000000..20f3c21
--- /dev/null
+++ b/android/arch/persistence/room/integration/testapp/vo/FunnyNamedEntity.java
@@ -0,0 +1,75 @@
+/*
+ * 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.persistence.room.integration.testapp.vo;
+
+import android.arch.persistence.room.ColumnInfo;
+import android.arch.persistence.room.Entity;
+import android.arch.persistence.room.PrimaryKey;
+
+/**
+ * An entity that was weird names
+ */
+@Entity(tableName = FunnyNamedEntity.TABLE_NAME)
+public class FunnyNamedEntity {
+    public static final String TABLE_NAME = "funny but not so funny";
+    public static final String COLUMN_ID = "_this $is id$";
+    public static final String COLUMN_VALUE = "unlikely-Ωşå¨ıünames";
+    @PrimaryKey(autoGenerate = true)
+    @ColumnInfo(name = COLUMN_ID)
+    private int mId;
+    @ColumnInfo(name = COLUMN_VALUE)
+    private String mValue;
+
+    public FunnyNamedEntity(int id, String value) {
+        mId = id;
+        mValue = value;
+    }
+
+    public int getId() {
+        return mId;
+    }
+
+    public void setId(int id) {
+        mId = id;
+    }
+
+    public String getValue() {
+        return mValue;
+    }
+
+    public void setValue(String value) {
+        mValue = value;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        FunnyNamedEntity entity = (FunnyNamedEntity) o;
+
+        if (mId != entity.mId) return false;
+        return mValue != null ? mValue.equals(entity.mValue) : entity.mValue == null;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mId;
+        result = 31 * result + (mValue != null ? mValue.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/android/arch/persistence/room/integration/testapp/vo/IntegerPKeyEntity.java b/android/arch/persistence/room/integration/testapp/vo/IntegerPKeyEntity.java
new file mode 100644
index 0000000..cae1843
--- /dev/null
+++ b/android/arch/persistence/room/integration/testapp/vo/IntegerPKeyEntity.java
@@ -0,0 +1,27 @@
+/*
+ * 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.persistence.room.integration.testapp.vo;
+
+import android.arch.persistence.room.Entity;
+import android.arch.persistence.room.PrimaryKey;
+
+@Entity
+public class IntegerPKeyEntity {
+    @PrimaryKey
+    public Integer pKey;
+    public String data;
+}
diff --git a/android/arch/persistence/room/integration/testapp/vo/ObjectPKeyEntity.java b/android/arch/persistence/room/integration/testapp/vo/ObjectPKeyEntity.java
new file mode 100644
index 0000000..895a35a
--- /dev/null
+++ b/android/arch/persistence/room/integration/testapp/vo/ObjectPKeyEntity.java
@@ -0,0 +1,34 @@
+/*
+ * 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.persistence.room.integration.testapp.vo;
+
+import android.arch.persistence.room.Entity;
+import android.arch.persistence.room.PrimaryKey;
+import android.support.annotation.NonNull;
+
+@Entity
+public class ObjectPKeyEntity {
+    @PrimaryKey
+    @NonNull
+    public String pKey;
+    public String data;
+
+    public ObjectPKeyEntity(String pKey, String data) {
+        this.pKey = pKey;
+        this.data = data;
+    }
+}
diff --git a/android/arch/persistence/room/integration/testapp/vo/PetCouple.java b/android/arch/persistence/room/integration/testapp/vo/PetCouple.java
index f27b131..e5208ed 100644
--- a/android/arch/persistence/room/integration/testapp/vo/PetCouple.java
+++ b/android/arch/persistence/room/integration/testapp/vo/PetCouple.java
@@ -20,11 +20,13 @@
 import android.arch.persistence.room.Entity;
 import android.arch.persistence.room.PrimaryKey;
 import android.arch.persistence.room.RoomWarnings;
+import android.support.annotation.NonNull;
 
 @Entity
 @SuppressWarnings(RoomWarnings.PRIMARY_KEY_FROM_EMBEDDED_IS_DROPPED)
 public class PetCouple {
     @PrimaryKey
+    @NonNull
     public String id;
     @Embedded(prefix = "male_")
     public Pet male;
diff --git a/android/arch/persistence/room/migration/TableInfoTest.java b/android/arch/persistence/room/migration/TableInfoTest.java
index c6eade5..d88c02f 100644
--- a/android/arch/persistence/room/migration/TableInfoTest.java
+++ b/android/arch/persistence/room/migration/TableInfoTest.java
@@ -37,6 +37,7 @@
 import org.junit.runner.RunWith;
 
 import java.io.IOException;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -179,6 +180,35 @@
                 Collections.<TableInfo.ForeignKey>emptySet())));
     }
 
+    @Test
+    public void readIndices() {
+        mDb = createDatabase(
+                "CREATE TABLE foo (n INTEGER, indexed TEXT, unique_indexed TEXT,"
+                        + "a INTEGER, b INTEGER);",
+                "CREATE INDEX foo_indexed ON foo(indexed);",
+                "CREATE UNIQUE INDEX foo_unique_indexed ON foo(unique_indexed COLLATE NOCASE"
+                        + " DESC);",
+                "CREATE INDEX " + TableInfo.Index.DEFAULT_PREFIX + "foo_composite_indexed"
+                        + " ON foo(a, b);"
+        );
+        TableInfo info = TableInfo.read(mDb, "foo");
+        assertThat(info, is(new TableInfo(
+                "foo",
+                toMap(new TableInfo.Column("n", "INTEGER", false, 0),
+                        new TableInfo.Column("indexed", "TEXT", false, 0),
+                        new TableInfo.Column("unique_indexed", "TEXT", false, 0),
+                        new TableInfo.Column("a", "INTEGER", false, 0),
+                        new TableInfo.Column("b", "INTEGER", false, 0)),
+                Collections.<TableInfo.ForeignKey>emptySet(),
+                toSet(new TableInfo.Index("index_foo_blahblah", false,
+                        Arrays.asList("a", "b")),
+                        new TableInfo.Index("foo_unique_indexed", true,
+                                Arrays.asList("unique_indexed")),
+                        new TableInfo.Index("foo_indexed", false,
+                                Arrays.asList("indexed"))))
+        ));
+    }
+
     private static Map<String, TableInfo.Column> toMap(TableInfo.Column... columns) {
         Map<String, TableInfo.Column> result = new HashMap<>();
         for (TableInfo.Column column : columns) {
@@ -187,6 +217,14 @@
         return result;
     }
 
+    private static <T> Set<T> toSet(T... ts) {
+        final HashSet<T> result = new HashSet<T>();
+        for (T t : ts) {
+            result.add(t);
+        }
+        return result;
+    }
+
     @After
     public void closeDb() throws IOException {
         if (mDb != null && mDb.isOpen()) {
@@ -199,8 +237,7 @@
                 SupportSQLiteOpenHelper.Configuration
                         .builder(InstrumentationRegistry.getTargetContext())
                         .name(null)
-                        .version(1)
-                        .callback(new SupportSQLiteOpenHelper.Callback() {
+                        .callback(new SupportSQLiteOpenHelper.Callback(1) {
                             @Override
                             public void onCreate(SupportSQLiteDatabase db) {
                                 for (String query : queries) {
diff --git a/android/arch/persistence/room/package-info.java b/android/arch/persistence/room/package-info.java
index faaa952..1dafc1b 100644
--- a/android/arch/persistence/room/package-info.java
+++ b/android/arch/persistence/room/package-info.java
@@ -39,8 +39,8 @@
  *     database row. For each {@link android.arch.persistence.room.Entity Entity}, a database table
  *     is created to hold the items. The Entity class must be referenced in the
  *     {@link android.arch.persistence.room.Database#entities() Database#entities} array. Each field
- *     of the Entity is persisted in the database unless it is annotated with
- *     {@link android.arch.persistence.room.Ignore Ignore}. Entities must have no-arg constructors.
+ *     of the Entity (and its super class) is persisted in the database unless it is denoted
+ *     otherwise (see {@link android.arch.persistence.room.Entity Entity} docs for details).
  *     </li>
  *     <li>{@link android.arch.persistence.room.Dao Dao}: This annotation marks a class or interface
  *     as a Data Access Object. Data access objects are the main component of Room that are
diff --git a/android/arch/persistence/room/testing/MigrationTestHelper.java b/android/arch/persistence/room/testing/MigrationTestHelper.java
index aea3e96..18e0a14 100644
--- a/android/arch/persistence/room/testing/MigrationTestHelper.java
+++ b/android/arch/persistence/room/testing/MigrationTestHelper.java
@@ -29,6 +29,7 @@
 import android.arch.persistence.room.migration.bundle.EntityBundle;
 import android.arch.persistence.room.migration.bundle.FieldBundle;
 import android.arch.persistence.room.migration.bundle.ForeignKeyBundle;
+import android.arch.persistence.room.migration.bundle.IndexBundle;
 import android.arch.persistence.room.migration.bundle.SchemaBundle;
 import android.arch.persistence.room.util.TableInfo;
 import android.content.Context;
@@ -146,7 +147,7 @@
         RoomOpenHelper roomOpenHelper = new RoomOpenHelper(configuration,
                 new CreatingDelegate(schemaBundle.getDatabase()),
                 schemaBundle.getDatabase().getIdentityHash());
-        return openDatabase(name, version, roomOpenHelper);
+        return openDatabase(name, roomOpenHelper);
     }
 
     /**
@@ -189,17 +190,15 @@
         RoomOpenHelper roomOpenHelper = new RoomOpenHelper(configuration,
                 new MigratingDelegate(schemaBundle.getDatabase(), validateDroppedTables),
                 schemaBundle.getDatabase().getIdentityHash());
-        return openDatabase(name, version, roomOpenHelper);
+        return openDatabase(name, roomOpenHelper);
     }
 
-    private SupportSQLiteDatabase openDatabase(String name, int version,
-            RoomOpenHelper roomOpenHelper) {
+    private SupportSQLiteDatabase openDatabase(String name, RoomOpenHelper roomOpenHelper) {
         SupportSQLiteOpenHelper.Configuration config =
                 SupportSQLiteOpenHelper.Configuration
                         .builder(mInstrumentation.getTargetContext())
                         .callback(roomOpenHelper)
                         .name(name)
-                        .version(version)
                         .build();
         SupportSQLiteDatabase db = mOpenFactory.create(config).getWritableDatabase();
         mManagedDatabases.add(new WeakReference<>(db));
@@ -287,7 +286,19 @@
 
     private static TableInfo toTableInfo(EntityBundle entityBundle) {
         return new TableInfo(entityBundle.getTableName(), toColumnMap(entityBundle),
-                toForeignKeys(entityBundle.getForeignKeys()));
+                toForeignKeys(entityBundle.getForeignKeys()), toIndices(entityBundle.getIndices()));
+    }
+
+    private static Set<TableInfo.Index> toIndices(List<IndexBundle> indices) {
+        if (indices == null) {
+            return Collections.emptySet();
+        }
+        Set<TableInfo.Index> result = new HashSet<>();
+        for (IndexBundle bundle : indices) {
+            result.add(new TableInfo.Index(bundle.getName(), bundle.isUnique(),
+                    bundle.getColumnNames()));
+        }
+        return result;
     }
 
     private static Set<TableInfo.ForeignKey> toForeignKeys(
@@ -401,6 +412,7 @@
         final DatabaseBundle mDatabaseBundle;
 
         RoomOpenHelperDelegate(DatabaseBundle databaseBundle) {
+            super(databaseBundle.getVersion());
             mDatabaseBundle = databaseBundle;
         }
 
diff --git a/android/arch/persistence/room/util/TableInfo.java b/android/arch/persistence/room/util/TableInfo.java
index bcd2e9e..a115147 100644
--- a/android/arch/persistence/room/util/TableInfo.java
+++ b/android/arch/persistence/room/util/TableInfo.java
@@ -20,6 +20,7 @@
 import android.database.Cursor;
 import android.os.Build;
 import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.annotation.RestrictTo;
 
 import java.util.ArrayList;
@@ -29,6 +30,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.TreeMap;
 
 /**
  * A data class that holds the information about a table.
@@ -56,11 +58,70 @@
 
     public final Set<ForeignKey> foreignKeys;
 
+    /**
+     * Sometimes, Index information is not available (older versions). If so, we skip their
+     * verification.
+     */
+    @Nullable
+    public final Set<Index> indices;
+
     @SuppressWarnings("unused")
-    public TableInfo(String name, Map<String, Column> columns, Set<ForeignKey> foreignKeys) {
+    public TableInfo(String name, Map<String, Column> columns, Set<ForeignKey> foreignKeys,
+            Set<Index> indices) {
         this.name = name;
         this.columns = Collections.unmodifiableMap(columns);
         this.foreignKeys = Collections.unmodifiableSet(foreignKeys);
+        this.indices = indices == null ? null : Collections.unmodifiableSet(indices);
+    }
+
+    /**
+     * For backward compatibility with dbs created with older versions.
+     */
+    @SuppressWarnings("unused")
+    public TableInfo(String name, Map<String, Column> columns, Set<ForeignKey> foreignKeys) {
+        this(name, columns, foreignKeys, Collections.<Index>emptySet());
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        TableInfo tableInfo = (TableInfo) o;
+
+        if (name != null ? !name.equals(tableInfo.name) : tableInfo.name != null) return false;
+        if (columns != null ? !columns.equals(tableInfo.columns) : tableInfo.columns != null) {
+            return false;
+        }
+        if (foreignKeys != null ? !foreignKeys.equals(tableInfo.foreignKeys)
+                : tableInfo.foreignKeys != null) {
+            return false;
+        }
+        if (indices == null || tableInfo.indices == null) {
+            // if one us is missing index information, seems like we couldn't acquire the
+            // information so we better skip.
+            return true;
+        }
+        return indices.equals(tableInfo.indices);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = name != null ? name.hashCode() : 0;
+        result = 31 * result + (columns != null ? columns.hashCode() : 0);
+        result = 31 * result + (foreignKeys != null ? foreignKeys.hashCode() : 0);
+        // skip index, it is not reliable for comparison.
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "TableInfo{"
+                + "name='" + name + '\''
+                + ", columns=" + columns
+                + ", foreignKeys=" + foreignKeys
+                + ", indices=" + indices
+                + '}';
     }
 
     /**
@@ -74,7 +135,8 @@
     public static TableInfo read(SupportSQLiteDatabase database, String tableName) {
         Map<String, Column> columns = readColumns(database, tableName);
         Set<ForeignKey> foreignKeys = readForeignKeys(database, tableName);
-        return new TableInfo(tableName, columns, foreignKeys);
+        Set<Index> indices = readIndices(database, tableName);
+        return new TableInfo(tableName, columns, foreignKeys, indices);
     }
 
     private static Set<ForeignKey> readForeignKeys(SupportSQLiteDatabase database,
@@ -167,34 +229,74 @@
         return columns;
     }
 
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-
-        TableInfo tableInfo = (TableInfo) o;
-
-        if (!name.equals(tableInfo.name)) return false;
-        //noinspection SimplifiableIfStatement
-        if (!columns.equals(tableInfo.columns)) return false;
-        return foreignKeys.equals(tableInfo.foreignKeys);
+    /**
+     * @return null if we cannot read the indices due to older sqlite implementations.
+     */
+    @Nullable
+    private static Set<Index> readIndices(SupportSQLiteDatabase database, String tableName) {
+        Cursor cursor = database.query("PRAGMA index_list(`" + tableName + "`)");
+        try {
+            final int nameColumnIndex = cursor.getColumnIndex("name");
+            final int originColumnIndex = cursor.getColumnIndex("origin");
+            final int uniqueIndex = cursor.getColumnIndex("unique");
+            if (nameColumnIndex == -1 || originColumnIndex == -1 || uniqueIndex == -1) {
+                // we cannot read them so better not validate any index.
+                return null;
+            }
+            HashSet<Index> indices = new HashSet<>();
+            while (cursor.moveToNext()) {
+                String origin = cursor.getString(originColumnIndex);
+                if (!"c".equals(origin)) {
+                    // Ignore auto-created indices
+                    continue;
+                }
+                String name = cursor.getString(nameColumnIndex);
+                boolean unique = cursor.getInt(uniqueIndex) == 1;
+                Index index = readIndex(database, name, unique);
+                if (index == null) {
+                    // we cannot read it properly so better not read it
+                    return null;
+                }
+                indices.add(index);
+            }
+            return indices;
+        } finally {
+            cursor.close();
+        }
     }
 
-    @Override
-    public int hashCode() {
-        int result = name.hashCode();
-        result = 31 * result + columns.hashCode();
-        result = 31 * result + foreignKeys.hashCode();
-        return result;
-    }
+    /**
+     * @return null if we cannot read the index due to older sqlite implementations.
+     */
+    @Nullable
+    private static Index readIndex(SupportSQLiteDatabase database, String name, boolean unique) {
+        Cursor cursor = database.query("PRAGMA index_xinfo(`" + name + "`)");
+        try {
+            final int seqnoColumnIndex = cursor.getColumnIndex("seqno");
+            final int cidColumnIndex = cursor.getColumnIndex("cid");
+            final int nameColumnIndex = cursor.getColumnIndex("name");
+            if (seqnoColumnIndex == -1 || cidColumnIndex == -1 || nameColumnIndex == -1) {
+                // we cannot read them so better not validate any index.
+                return null;
+            }
+            final TreeMap<Integer, String> results = new TreeMap<>();
 
-    @Override
-    public String toString() {
-        return "TableInfo{"
-                + "name='" + name + '\''
-                + ", columns=" + columns
-                + ", foreignKeys=" + foreignKeys
-                + '}';
+            while (cursor.moveToNext()) {
+                int cid = cursor.getInt(cidColumnIndex);
+                if (cid < 0) {
+                    // Ignore SQLite row ID
+                    continue;
+                }
+                int seq = cursor.getInt(seqnoColumnIndex);
+                String columnName = cursor.getString(nameColumnIndex);
+                results.put(seq, columnName);
+            }
+            final List<String> columns = new ArrayList<>(results.size());
+            columns.addAll(results.values());
+            return new Index(name, unique, columns);
+        } finally {
+            cursor.close();
+        }
     }
 
     /**
@@ -379,4 +481,65 @@
             }
         }
     }
+
+    /**
+     * Holds the information about an SQLite index
+     *
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public static class Index {
+        // should match the value in Index.kt
+        public static final String DEFAULT_PREFIX = "index_";
+        public final String name;
+        public final boolean unique;
+        public final List<String> columns;
+
+        public Index(String name, boolean unique, List<String> columns) {
+            this.name = name;
+            this.unique = unique;
+            this.columns = columns;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            Index index = (Index) o;
+            if (unique != index.unique) {
+                return false;
+            }
+            if (!columns.equals(index.columns)) {
+                return false;
+            }
+            if (name.startsWith(Index.DEFAULT_PREFIX)) {
+                return index.name.startsWith(Index.DEFAULT_PREFIX);
+            } else {
+                return name.equals(index.name);
+            }
+        }
+
+        @Override
+        public int hashCode() {
+            int result;
+            if (name.startsWith(DEFAULT_PREFIX)) {
+                result = DEFAULT_PREFIX.hashCode();
+            } else {
+                result = name.hashCode();
+            }
+            result = 31 * result + (unique ? 1 : 0);
+            result = 31 * result + columns.hashCode();
+            return result;
+        }
+
+        @Override
+        public String toString() {
+            return "Index{"
+                    + "name='" + name + '\''
+                    + ", unique=" + unique
+                    + ", columns=" + columns
+                    + '}';
+        }
+    }
 }